Skip to content

CI

CI #657

Workflow file for this run

name: CI
on:
push:
branches: [ main, develop, feature/add-ci ]
pull_request:
branches: [ main, develop ]
schedule:
# Run tests daily at 2 AM UTC
- cron: '0 2 * * *'
permissions:
pull-requests: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
lint_and_format:
name: Lint and Format
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-
- name: Cache cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-index-
- name: Cache cargo target
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-build-target-
- name: Check formatting
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if ! cargo fmt --all -- --check; then
# Only comment on Pull Request events
if [ -n "$PR_NUMBER" ]; then
MARKER="<!-- gemini-fmt-error -->"
BODY="$MARKER
⚠️ **ERROR:** Code formatting issues detected. Please run \`cargo fmt --all\` locally and commit the changes."
# Check if a comment with our marker already exists to avoid spamming
existing_comment=$(gh pr view "$PR_NUMBER" --json comments -q ".comments[] | select(.body | contains(\"$MARKER\")) | .id")
if [ -z "$existing_comment" ]; then
gh pr comment "$PR_NUMBER" --body "$BODY"
else
echo "A formatting error comment already exists. Skipping."
fi
fi
exit 1
fi
- name: Check with clippy
if: always()
run: |
set -o pipefail
cargo clippy --all-targets --all-features --message-format=json -- -D warnings | jq -r 'select(.reason == "compiler-message" and .message.spans[0].is_primary) | .message | "::warning file=\(.spans[0].file_name),line=\(.spans[0].line_start)::\(.message)"'
security_audit:
name: Security Audit
runs-on: ubuntu-latest
container: rust:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m')" >> $GITHUB_OUTPUT
- name: Cache cargo-audit
uses: actions/cache@v4
with:
path: /usr/local/cargo/bin/cargo-audit
key: ${{ runner.os }}-cargo-audit-${{ steps.date.outputs.date }}
- name: Run security audit
run: |
if [ ! -f "/usr/local/cargo/bin/cargo-audit" ]; then
cargo install cargo-audit
fi
cargo audit
msrv:
name: Minimum Supported Rust Version
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Read MSRV from Cargo.toml
id: read_msrv
run: |
msrv=$(awk -F '"' '/rust-version/ {print $2}' Cargo.toml)
echo "msrv=$msrv" >> $GITHUB_OUTPUT
- name: Install MSRV Rust (${{ steps.read_msrv.outputs.msrv }})
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ steps.read_msrv.outputs.msrv }}
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config
- name: Check if project builds with MSRV
run: cargo build --locked --verbose
build_and_test:
name: Build and Test
needs:
- lint_and_format
- security_audit
- msrv
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macOS-latest]
rust: [stable]
include:
# Run jobs both on the Ubuntu host and in a CentOS container
- os: ubuntu-latest
container: ''
rust: stable
- os: ubuntu-latest
container: ''
rust: beta
- os: ubuntu-latest
container: 'quay.io/centos/centos:stream9'
rust: stable
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-
- name: Cache cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-index-
- name: Cache cargo target
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-build-target-
- name: Install Ubuntu dependencies
if: matrix.os == 'ubuntu-latest' && !matrix.container
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config
- name: Install CentOS dependencies
if: matrix.container
run: |
dnf install -y gcc pkgconf-pkg-config
- name: Run tests
run: cargo test --verbose --all-features
- name: Build release
run: cargo build --release --verbose
- name: Run benchmark (dry run)
run: cargo run --release -- --help
coverage:
name: Coverage
runs-on: ubuntu-latest
needs: build_and_test
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request')
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Fetch the full history to allow diff-coverage checking
fetch-depth: 0
- name: Install Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config
- name: Install cargo-tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage report
run: |
mkdir -p ./coverage-reports
cargo tarpaulin \
--verbose \
--all-features \
--workspace \
--engine llvm \
--timeout 120 \
--out Xml \
--color never \
--output-dir ./coverage-reports \
| tee ./coverage-reports/coverage-summary.txt
- name: Post detailed coverage summary to Job Summary
run: |
echo "### Code Coverage Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '<details><summary>Click to expand for a detailed, per-file coverage report</summary>' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```text' >> $GITHUB_STEP_SUMMARY
cat ./coverage-reports/coverage-summary.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '</details>' >> $GITHUB_STEP_SUMMARY
- name: Generate Markdown Summary Table
run: |
python3 -c '
import xml.etree.ElementTree as ET
import os
def to_ranges(numbers):
if not numbers:
return ""
numbers = sorted([int(n) for n in numbers])
ranges = []
start = end = numbers[0]
for n in numbers[1:]:
if n == end + 1:
end = n
else:
if start == end:
ranges.append(str(start))
else:
ranges.append(f"{start}-{end}")
start = end = n
if start == end:
ranges.append(str(start))
else:
ranges.append(f"{start}-{end}")
return ", ".join(ranges)
tree = ET.parse("./coverage-reports/cobertura.xml")
root = tree.getroot()
line_rate = float(root.attrib["line-rate"]) * 100
total_lines_all = int(root.attrib.get("lines-valid", "0"))
total_covered_all = int(root.attrib.get("lines-covered", "0"))
with open("./coverage-reports/coverage-summary.md", "w") as f:
f.write("### 📊 Code Coverage Summary\n\n")
f.write("| File | Line Coverage | Uncovered Lines |\n")
f.write("|------|---------------|-----------------|\n")
packages = root.find("packages")
all_classes = []
if packages is not None:
for package in packages.findall("package"):
classes = package.find("classes")
if classes is not None:
for klass in classes.findall("class"):
all_classes.append(klass)
all_classes.sort(key=lambda x: x.attrib["filename"])
for klass in all_classes:
filename = klass.attrib["filename"]
if "target/debug/build" in filename:
continue
line_rate_class = float(klass.attrib["line-rate"]) * 100
total_lines_class = 0
covered_lines_class = 0
uncovered_lines = []
lines = klass.find("lines")
if lines is not None:
for line in lines.findall("line"):
total_lines_class += 1
if int(line.attrib.get("hits", "0")) > 0:
covered_lines_class += 1
if line.attrib["hits"] == "0":
uncovered_lines.append(line.attrib["number"])
uncovered_lines_str = to_ranges(uncovered_lines)
f.write(f"| `{filename}` | {line_rate_class:.2f}%<br>({covered_lines_class}/{total_lines_class}) | `{uncovered_lines_str}` |\n")
f.write(f"| **Total** | **{line_rate:.2f}%**<br>({total_covered_all}/{total_lines_all}) | | \n")
'
- name: Add Markdown summary table to Job Summary
run: |
cat ./coverage-reports/coverage-summary.md >> $GITHUB_STEP_SUMMARY
- name: Archive coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
./coverage-reports/
retention-days: 1
coverage_pr_comment:
name: Post PR Coverage Comment
runs-on: ubuntu-latest
needs: coverage
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Fetch the full history to allow diff-coverage checking
fetch-depth: 0
- name: Download coverage report artifact
uses: actions/download-artifact@v4
with:
name: coverage-reports
path: ./coverage-reports/
- name: Check for uncovered lines in PR
id: pr_coverage_check
run: |
set -e
{
echo 'PR_COVERAGE_REPORT<<EOF'
./.github/scripts/check_pr_coverage.py ${{ github.base_ref }}
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Post Coverage Comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_COVERAGE_REPORT: ${{ steps.pr_coverage_check.outputs.PR_COVERAGE_REPORT }}
run: |
MARKER="<!-- gemini-coverage-comment -->"
BODY=$(cat ./coverage-reports/coverage-summary.md)
COMMENT_BODY="$MARKER
$PR_COVERAGE_REPORT
$BODY"
gh pr comment "$PR_NUMBER" --body "$COMMENT_BODY"
benchmarks:
name: Performance Benchmarks
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-bench-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-bench-
- name: Run benchmarks
run: cargo bench --verbose
- name: Store benchmark results
uses: benchmark-action/github-action-benchmark@v1
if: github.ref == 'refs/heads/main'
with:
tool: 'cargo'
output-file-path: target/criterion/reports/index.html
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
podman:
name: Container Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build container image
run: podman build -t ipc-benchmark -f Containerfile .
- name: Test container image
run: podman run --rm ipc-benchmark --help