ci: consolidate GitHub Actions workflows (#427)

Consolidate and improve GitHub Actions workflows:

Deleted redundant/outdated workflows:
- claude-code-review.yml - No longer used
- claude.yml - No longer used
- pre-commit.yml - Merged into static-checks.yml

Renamed for clarity:
- comp-integration.yml → component-integration.yml
- integration.yml → system-integration.yml

New workflows:
- static-checks.yml - Comprehensive static analysis (shellcheck, hadolint, actionlint)

Improvements:
- Add actionlint configuration (.github/actionlint.yaml)
- Update dependabot configuration with cooldown settings
- Improve workflow concurrency settings
- Pin actions to SHA hashes for security
- Add better job dependencies and caching

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dan Guido
2026-01-26 05:46:53 -05:00
committed by GitHub
parent 0206c0f6e1
commit c32fe7a767
11 changed files with 224 additions and 287 deletions
+6
View File
@@ -0,0 +1,6 @@
# actionlint configuration
# https://github.com/rhysd/actionlint/blob/main/docs/config.md
self-hosted-runner:
labels:
- gha-ubuntu-8
+8 -2
View File
@@ -8,7 +8,10 @@ updates:
patterns:
- "*"
schedule:
interval: daily
interval: weekly
# Supply chain security: delay updates to allow community vetting
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: /
@@ -17,4 +20,7 @@ updates:
patterns:
- "*"
schedule:
interval: daily
interval: weekly
# Supply chain security: delay updates to allow community vetting
cooldown:
default-days: 7
-103
View File
@@ -1,103 +0,0 @@
name: Claude Code Review
on:
pull_request:
types: [labeled, synchronize]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Only run when PR has the 'claude-review' label
if: contains(github.event.pull_request.labels.*.name, 'claude-review')
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write # Required for OIDC authentication (used by Codecov and other services)
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
submodules: true
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
# model: "claude-opus-4-1-20250805"
# Direct prompt for automated review (no @claude mention needed)
direct_prompt: |
Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage
Be constructive and helpful in your feedback.
# Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
use_sticky_comment: true
# Optional: Customize review based on file types
# direct_prompt: |
# Review this PR focusing on:
# - For TypeScript files: Type safety and proper interface usage
# - For API endpoints: Security, input validation, and error handling
# - For React components: Performance, accessibility, and best practices
# - For tests: Coverage, edge cases, and test quality
# Optional: Different prompts for different authors
# direct_prompt: |
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
# Add specific tools for running tests or linting
allowed_tools: |
Bash(make *),
Bash(cd * && make *),
Bash(uv *),
Bash(cd * && uv *),
Bash(pytest*),
Bash(ruff check*),
Bash(ruff format*),
Bash(mypy*),
Bash(git status*),
Bash(git diff*),
Bash(git log*),
Bash(git branch*),
Bash(git show*),
Bash(ls*),
Bash(cat *),
Bash(head *),
Bash(tail *),
Bash(grep *),
Bash(find * -name *),
Bash(pwd),
Bash(echo *),
Bash(which *),
Bash(pip list),
Bash(pip show *),
Bash(python --version),
Bash(python -m *)
# Optional: Skip review for certain conditions
# if: |
# !contains(github.event.pull_request.title, '[skip-review]') &&
# !contains(github.event.pull_request.title, '[WIP]')
-104
View File
@@ -1,104 +0,0 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write # Required for OIDC authentication (used by Codecov and other services)
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
submodules: true
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
# model: "claude-opus-4-1-20250805"
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
# Allow Claude to run specific commands
allowed_tools: |
Bash(make *),
Bash(cd * && make *),
Bash(uv *),
Bash(cd * && uv *),
Bash(pytest*),
Bash(ruff check*),
Bash(ruff format*),
Bash(mypy*),
Bash(git status*),
Bash(git diff*),
Bash(git log*),
Bash(git branch*),
Bash(git show*),
Bash(git add*),
Bash(git commit*),
Bash(git push),
Bash(ls*),
Bash(cat *),
Bash(head *),
Bash(tail *),
Bash(grep *),
Bash(find * -name *),
Bash(pwd),
Bash(echo *),
Bash(which *),
Bash(pip list),
Bash(pip show *),
Bash(python --version),
Bash(python -m *),
Bash(npm run*),
Bash(pnpm run*),
Bash(./scripts/setup-local.sh),
Bash(./scripts/setup-azure.sh),
Bash(./scripts/validate-setup.sh),
Bash(./deployment/collect-logs.sh),
Bash(./deployment/collect-data.sh),
Bash(./orchestrator/scripts/task_crs.sh),
Bash(./orchestrator/scripts/send_sarif.sh)
# Optional: Add custom instructions for Claude to customize its behavior for your project
# custom_instructions: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Custom environment variables for Claude
# claude_env: |
# NODE_ENV: test
@@ -30,15 +30,18 @@ concurrency:
jobs:
# Integration tests - run on schedule, manual trigger, or labeled PRs
test-integration:
permissions:
contents: read
# Run if:
# - Scheduled run
# - Manual dispatch with run_integration=true
# - Push to main branch
# - PR with 'integration-tests' label
if: |
github.event_name == 'push' ||
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_integration == 'true') ||
contains(github.event.pull_request.labels.*.name, 'integration-tests')
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'integration-tests'))
strategy:
fail-fast: false
@@ -60,7 +63,7 @@ jobs:
runs-on: ubuntu-latest
services:
redis:
image: redis
image: redis@sha256:e647cfe134bf5e8e74e620f66346f93418acfc240b71dd85640325cb7cd01402 # 7.4
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
@@ -70,7 +73,7 @@ jobs:
- 6379:6379
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
submodules: true
@@ -78,9 +81,11 @@ jobs:
- name: Check if component should be tested
id: should_test
if: github.event_name == 'workflow_dispatch'
env:
INPUT_COMPONENTS: ${{ github.event.inputs.components }}
MATRIX_COMPONENT: ${{ matrix.component }}
run: |
components="${{ github.event.inputs.components }}"
if [[ -z "$components" ]] || [[ "$components" == *"${{ matrix.component }}"* ]]; then
if [[ -z "$INPUT_COMPONENTS" ]] || [[ "$INPUT_COMPONENTS" == *"$MATRIX_COMPONENT"* ]]; then
echo "test=true" >> $GITHUB_OUTPUT
else
echo "test=false" >> $GITHUB_OUTPUT
@@ -88,11 +93,11 @@ jobs:
- name: Install uv
if: steps.should_test.outputs.test != 'false'
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Setup uv cache
if: steps.should_test.outputs.test != 'false'
uses: actions/cache@v4
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with:
path: |
~/.cache/uv
@@ -126,7 +131,7 @@ jobs:
if: steps.should_test.outputs.test != 'false'
run: |
uv sync --all-extras --frozen
uv pip install --isolated pytest-html>=4.1.1 pytest-cov>=6.0.0
uv pip install --isolated 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0'
working-directory: ${{ matrix.component }}
- name: Run program-model libpng integration test
@@ -189,7 +194,7 @@ jobs:
- name: Upload integration test results
if: always() && steps.should_test.outputs.test != 'false'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: integration-test-results-${{ matrix.component }}-py${{ matrix.python }}
path: |
+10 -5
View File
@@ -45,16 +45,21 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
submodules: true
- name: Lint Dockerfile
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: ${{ matrix.dockerfile }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
@@ -62,7 +67,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}-${{ matrix.component }}
tags: |
@@ -73,7 +78,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}},suffix=${{ inputs.suffix }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
file: ${{ matrix.dockerfile }}
+29 -11
View File
@@ -11,6 +11,7 @@ on:
- "common/**"
- "orchestrator/**"
- "fuzzer/**"
- "fuzzer_runner/**"
- "program-model/**"
- "seed-gen/**"
- "patcher/**"
@@ -26,21 +27,29 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Detect which components need linting based on changed files
changes:
permissions:
contents: read
runs-on: ubuntu-latest
outputs:
common: ${{ steps.filter.outputs.common }}
orchestrator: ${{ steps.filter.outputs.orchestrator }}
fuzzer: ${{ steps.filter.outputs.fuzzer }}
fuzzer_runner: ${{ steps.filter.outputs.fuzzer_runner }}
program-model: ${{ steps.filter.outputs.program-model }}
seed-gen: ${{ steps.filter.outputs.seed-gen }}
patcher: ${{ steps.filter.outputs.patcher }}
run-all: ${{ steps.filter.outputs.workflow || steps.filter.outputs.global }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
with:
filters: |
@@ -50,6 +59,8 @@ jobs:
- 'orchestrator/**'
fuzzer:
- 'fuzzer/**'
fuzzer_runner:
- 'fuzzer_runner/**'
program-model:
- 'program-model/**'
seed-gen:
@@ -64,6 +75,8 @@ jobs:
- '**/*.proto'
matrix-generator:
permissions:
contents: read
runs-on: ubuntu-latest
needs: changes
outputs:
@@ -74,22 +87,25 @@ jobs:
run: |
components=()
if [[ "${{ needs.changes.outputs.run-all }}" == "true" ]]; then
components=("common" "orchestrator" "fuzzer" "program-model" "seed-gen" "patcher")
components=("common" "orchestrator" "fuzzer" "fuzzer_runner" "program-model" "seed-gen" "patcher")
else
[[ "${{ needs.changes.outputs.common }}" == "true" ]] && components+=("common")
[[ "${{ needs.changes.outputs.orchestrator }}" == "true" ]] && components+=("orchestrator")
[[ "${{ needs.changes.outputs.fuzzer }}" == "true" ]] && components+=("fuzzer")
[[ "${{ needs.changes.outputs.fuzzer_runner }}" == "true" ]] && components+=("fuzzer_runner")
[[ "${{ needs.changes.outputs.program-model }}" == "true" ]] && components+=("program-model")
[[ "${{ needs.changes.outputs.seed-gen }}" == "true" ]] && components+=("seed-gen")
[[ "${{ needs.changes.outputs.patcher }}" == "true" ]] && components+=("patcher")
fi
if [[ ${#components[@]} -eq 0 ]]; then
echo 'matrix={"component":[]}' >> $GITHUB_OUTPUT
echo 'matrix={"component":[]}' >> "$GITHUB_OUTPUT"
else
echo "matrix={\"component\":[$(printf '"%s",' "${components[@]}" | sed 's/,$//')]}" >> $GITHUB_OUTPUT
echo "matrix={\"component\":[$(printf '"%s",' "${components[@]}" | sed 's/,$//')]}" >> "$GITHUB_OUTPUT"
fi
lint:
permissions:
contents: read
needs: [changes, matrix-generator]
strategy:
fail-fast: false # Continue running other components even if one fails
@@ -97,15 +113,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Setup uv cache
uses: actions/cache@v4
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with:
path: |
~/.cache/uv
@@ -125,17 +141,19 @@ jobs:
uv run ruff check --output-format=github
working-directory: ${{ matrix.component }}
- name: Run mypy on ${{ matrix.component }}
run: uv run mypy
- name: Run ty on ${{ matrix.component }}
run: uv run ty check src/
working-directory: ${{ matrix.component }}
check-protobuf:
permissions:
contents: read
needs: changes
# Run if protobuf files changed or we need to run all
if: needs.changes.outputs.run-all == 'true' || contains(github.event.head_commit.modified, '.proto') || contains(github.event.head_commit.modified, 'protoc.sh')
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
-20
View File
@@ -1,20 +0,0 @@
name: Pre-commit
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- uses: astral-sh/setup-uv@v6
- uses: pre-commit/action@v3.0.1
+82
View File
@@ -0,0 +1,82 @@
name: Static Checks
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
static-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
enable-cache: true
- name: Set up Python
run: uv python install 3.13
- name: Lint GitHub Actions
run: |
# Install actionlint
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
./actionlint -color
- name: Security audit GitHub Actions
run: |
# Install zizmor
uv pip install --isolated zizmor
zizmor .github/workflows/
- name: Check YAML files
run: |
python3 -c "
import yaml
from pathlib import Path
for f in Path('.').rglob('*.yaml'):
if 'deployment/k8s' not in str(f):
list(yaml.safe_load_all(f.read_text()))
for f in Path('.').rglob('*.yml'):
if 'deployment/k8s' not in str(f):
list(yaml.safe_load_all(f.read_text()))
"
- name: Check TOML files
run: |
python3 -c "
import tomllib
from pathlib import Path
for f in Path('.').rglob('*.toml'):
tomllib.load(f.open('rb'))
"
- name: Check JSON files
run: |
python3 -c "
import json
from pathlib import Path
for f in Path('.').rglob('*.json'):
json.load(f.open())
"
- name: Check for merge conflicts
run: |
! grep -rE '^(<{7}|>{7}|={7})' --include='*.py' --include='*.yaml' --include='*.yml' --include='*.toml' --include='*.json' . || exit 1
- name: Lint shell scripts
run: |
shellcheck --version
find . -name '*.sh' -type f ! -path './.git/*' ! -path '*/.venv/*' -print0 | xargs -0 shellcheck
- name: Lint Dockerfiles
run: |
hadolint --version
find . -name 'Dockerfile*' -type f ! -path './.git/*' -print0 | xargs -0 hadolint
@@ -58,7 +58,6 @@ env:
permissions:
contents: read
packages: write
jobs:
integration:
@@ -69,7 +68,7 @@ jobs:
contains(github.event.pull_request.labels.*.name, 'full-integration')
runs-on: gha-ubuntu-8
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
submodules: true
@@ -88,20 +87,41 @@ jobs:
echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NAMESPACE}" >> $GITHUB_ENV
- name: Configure env file for minikube
env:
BUTTERCUP_NS: ${{ env.BUTTERCUP_NAMESPACE }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GEMINI_KEY: ${{ secrets.GEMINI_API_KEY }}
LF_HOST: ${{ secrets.LANGFUSE_HOST }}
LF_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }}
LF_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }}
OTEL_EP: ${{ secrets.OTEL_ENDPOINT }}
OTEL_TK: ${{ secrets.OTEL_TOKEN }}
run: |
cp ../.github/ci-env.template env
sed -i "/GHCR_AUTH=.*/d" env
sed -i "s|BUTTERCUP_NAMESPACE=.*|BUTTERCUP_NAMESPACE=${{ env.BUTTERCUP_NAMESPACE }}|" env
sed -i "s|OPENAI_API_KEY=.*|OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}|" env
sed -i "s|ANTHROPIC_API_KEY=.*|ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}|" env
sed -i "s|GEMINI_API_KEY=.*|GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }}|" env
sed -i "s|LANGFUSE_HOST=.*|LANGFUSE_HOST=${{ secrets.LANGFUSE_HOST }}|" env
sed -i "s|LANGFUSE_PUBLIC_KEY=.*|LANGFUSE_PUBLIC_KEY=${{ secrets.LANGFUSE_PUBLIC_KEY }}|" env
sed -i "s|LANGFUSE_SECRET_KEY=.*|LANGFUSE_SECRET_KEY=${{ secrets.LANGFUSE_SECRET_KEY }}|" env
sed -i "s|OTEL_ENDPOINT=.*|OTEL_ENDPOINT=${{ secrets.OTEL_ENDPOINT }}|" env
sed -i "s|OTEL_TOKEN=.*|OTEL_TOKEN=\"${{ secrets.OTEL_TOKEN }}\"|" env
# Remove GHCR_AUTH line and lines for values we'll append
sed -i "/^GHCR_AUTH=/d" env
sed -i "/^BUTTERCUP_NAMESPACE=/d" env
sed -i "/^OPENAI_API_KEY=/d" env
sed -i "/^ANTHROPIC_API_KEY=/d" env
sed -i "/^GEMINI_API_KEY=/d" env
sed -i "/^LANGFUSE_HOST=/d" env
sed -i "/^LANGFUSE_PUBLIC_KEY=/d" env
sed -i "/^LANGFUSE_SECRET_KEY=/d" env
sed -i "/^OTEL_ENDPOINT=/d" env
sed -i "/^OTEL_TOKEN=/d" env
# Append values safely using echo (avoids sed injection with special chars)
{
echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NS}"
echo "OPENAI_API_KEY=${OPENAI_KEY}"
echo "ANTHROPIC_API_KEY=${ANTHROPIC_KEY}"
echo "GEMINI_API_KEY=${GEMINI_KEY}"
echo "LANGFUSE_HOST=${LF_HOST}"
echo "LANGFUSE_PUBLIC_KEY=${LF_PUBLIC_KEY}"
echo "LANGFUSE_SECRET_KEY=${LF_SECRET_KEY}"
echo "OTEL_ENDPOINT=${OTEL_EP}"
echo "OTEL_TOKEN=${OTEL_TK}"
} >> "env"
working-directory: deployment
- name: Run CRS
+40 -18
View File
@@ -11,8 +11,13 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
permissions:
contents: read
strategy:
fail-fast: false # Continue running other components even if one fails
matrix:
@@ -35,12 +40,15 @@ jobs:
- component: fuzzer
coverage_module: buttercup.fuzzer
python: "3.12"
- component: fuzzer_runner
coverage_module: buttercup.fuzzer_runner
python: "3.12"
runs-on: ubuntu-latest
# Removed if: matrix.should_run since we're not using path filtering right now
services:
redis:
image: redis
image: redis@sha256:e647cfe134bf5e8e74e620f66346f93418acfc240b71dd85640325cb7cd01402 # 7.4
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
@@ -50,16 +58,16 @@ jobs:
- 6379:6379
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
submodules: true
- name: Install uv
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Setup uv cache
uses: actions/cache@v4
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with:
path: |
~/.cache/uv
@@ -82,14 +90,14 @@ jobs:
make install-cscope
- name: Install minimal dependencies
if: matrix.component != 'program-model' && matrix.component != 'seed-gen' && matrix.component != 'patcher' && matrix.component != 'fuzzer'
if: matrix.component != 'program-model' && matrix.component != 'seed-gen' && matrix.component != 'patcher' && matrix.component != 'fuzzer' && matrix.component != 'fuzzer_runner'
run: |
sudo apt-get update
sudo apt-get install -y ripgrep
# Fuzzer only needs ripgrep, no codequery
# Fuzzer and fuzzer_runner only need ripgrep, no codequery
- name: Install fuzzer dependencies
if: matrix.component == 'fuzzer'
if: matrix.component == 'fuzzer' || matrix.component == 'fuzzer_runner'
run: |
sudo apt-get update
sudo apt-get install -y ripgrep
@@ -106,12 +114,12 @@ jobs:
uv sync --all-extras --frozen
# Install test reporting tools in isolated environment
# This avoids adding them to every component's dependencies
uv pip install --isolated pytest-html>=4.1.1 pytest-cov>=6.0.0
uv pip install --isolated 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0'
working-directory: ${{ matrix.component }}
- name: Run tests on ${{ matrix.component }} component
run: |
uv run --frozen pytest -svv ${{ matrix.pytest_args }} \
uv run --frozen pytest -svv \
--junit-xml=test-results.xml \
--html=test-report.html \
--self-contained-html \
@@ -123,12 +131,24 @@ jobs:
PYTHON_WASM_BUILD_PATH: "python-3.12.0.wasm"
working-directory: ${{ matrix.component }}
- name: Audit dependencies for vulnerabilities
if: always()
run: |
uv pip install --isolated pip-audit
# Ignore CVEs with no available fix:
# - CVE-2025-67221: orjson DoS via deeply nested JSON (no fix available)
# - CVE-2026-0994: protobuf DoS via json_format.ParseDict (no fix available)
uv run pip-audit --strict --desc \
--ignore-vuln CVE-2025-67221 \
--ignore-vuln CVE-2026-0994
working-directory: ${{ matrix.component }}
- name: Generate test summary
if: always()
run: |
echo "### Test Results: ${{ matrix.component }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f ${{ matrix.component }}/test-results.xml ]; then
echo "### Test Results: ${{ matrix.component }}" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
if [ -f "${{ matrix.component }}/test-results.xml" ]; then
python -c "
import xml.etree.ElementTree as ET
tree = ET.parse('${{ matrix.component }}/test-results.xml')
@@ -144,14 +164,14 @@ jobs:
print(f'- **Errors**: {errors}')
print(f'- **Skipped**: {skipped}')
print(f'- **Duration**: {float(time):.2f}s')
" >> $GITHUB_STEP_SUMMARY
" >> "$GITHUB_STEP_SUMMARY"
else
echo "No test results found" >> $GITHUB_STEP_SUMMARY
echo "No test results found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: test-results-${{ matrix.component }}-py${{ matrix.python }}
path: |
@@ -165,18 +185,20 @@ jobs:
# Consolidated coverage upload after all tests complete
coverage-upload:
permissions:
contents: read
needs: [test]
if: always()
runs-on: ubuntu-latest
steps:
- name: Download all coverage reports
uses: actions/download-artifact@v5
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: test-results-*
path: coverage-reports
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
directory: coverage-reports
files: '*/coverage.xml,**/coverage.xml'