Skip to content

fix(indexer): defer final index-run metadata update on targeted watch… #2102

fix(indexer): defer final index-run metadata update on targeted watch…

fix(indexer): defer final index-run metadata update on targeted watch… #2102

Workflow file for this run

# .github/workflows/ci.yml
# Continuous Integration: lint, test, audit, and build verification
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUST_LOG: debug
jobs:
# No-mock policy audit
no-mock-audit:
name: No-Mock Policy Audit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Run repository artifact hygiene check
run: ./scripts/validate_ci.sh --artifact-hygiene-only
- name: Install ripgrep
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Run no-mock audit
id: audit
shell: bash
run: |
set -euo pipefail
mkdir -p test-results
ALLOWLIST_FILE="tests/policies/no_mock_allowlist.json"
AUDIT_REPORT="test-results/no_mock_ci_audit.md"
echo "# No-Mock CI Audit" > "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "**Run:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$AUDIT_REPORT"
echo "**Commit:** ${{ github.sha }}" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
# Search for mock/fake/stub patterns
VIOLATIONS=$(mktemp)
rg -n "(Mock[A-Z][a-z]|Fake[A-Z][a-z]|Stub[A-Z][a-z]|mock_|fake_|stub_)" \
--glob '!**/node_modules/**' \
--glob '!target/**' \
--glob '!.git/**' \
--glob '!tests/fixtures/**' \
--glob '!test-results/**' \
--glob '!*.md' \
--glob '!*.json' \
src/ tests/ 2>/dev/null > "$VIOLATIONS" || true
VIOLATION_COUNT=$(wc -l < "$VIOLATIONS" | tr -d ' ')
if [ "$VIOLATION_COUNT" -eq 0 ]; then
echo "## Status: ✅ PASS" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "No mock/fake/stub patterns found." >> "$AUDIT_REPORT"
echo "status=pass" >> "$GITHUB_OUTPUT"
rm -f "$VIOLATIONS"
exit 0
fi
echo "Found $VIOLATION_COUNT pattern(s), checking allowlist..."
if [ ! -f "$ALLOWLIST_FILE" ]; then
echo "## Status: ❌ FAIL" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "Allowlist file not found: $ALLOWLIST_FILE" >> "$AUDIT_REPORT"
echo "status=fail" >> "$GITHUB_OUTPUT"
rm -f "$VIOLATIONS"
exit 1
fi
ALLOWLIST_ENTRIES=$(jq -r '.entries[] | "\(.path):\(.pattern)"' "$ALLOWLIST_FILE" 2>/dev/null || echo "")
UNALLOWED_COUNT=0
UNALLOWED_LIST=""
while IFS= read -r line; do
FILE=$(echo "$line" | cut -d: -f1)
PATTERN=$(echo "$line" | grep -oiE "(Mock[A-Z][a-zA-Z]*|Fake[A-Z][a-zA-Z]*|Stub[A-Z][a-zA-Z]*|mock_[a-z_]+|fake_[a-z_]+|stub_[a-z_]+)" | head -1)
ALLOWED=false
for entry in $ALLOWLIST_ENTRIES; do
ENTRY_PATH=$(echo "$entry" | cut -d: -f1)
ENTRY_PATTERN=$(echo "$entry" | cut -d: -f2)
if [[ "$FILE" == *"$ENTRY_PATH"* ]] && [[ "$PATTERN" == *"$ENTRY_PATTERN"* || "$ENTRY_PATTERN" == *"$PATTERN"* ]]; then
ALLOWED=true
break
fi
done
if [ "$ALLOWED" = false ]; then
UNALLOWED_COUNT=$((UNALLOWED_COUNT + 1))
UNALLOWED_LIST="${UNALLOWED_LIST}\n- \`${line}\`"
fi
done < "$VIOLATIONS"
rm -f "$VIOLATIONS"
if [ "$UNALLOWED_COUNT" -gt 0 ]; then
echo "## Status: ❌ FAIL" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "**Unapproved patterns:** $UNALLOWED_COUNT" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "### Violations" >> "$AUDIT_REPORT"
echo -e "$UNALLOWED_LIST" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "### How to Fix" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "1. Replace mock/fake/stub with real fixtures (preferred)" >> "$AUDIT_REPORT"
echo "2. OR add to \`tests/policies/no_mock_allowlist.json\` with:" >> "$AUDIT_REPORT"
echo " - \`rationale\`: Why this exception is necessary" >> "$AUDIT_REPORT"
echo " - \`review_date\`: 6-month review date" >> "$AUDIT_REPORT"
echo " - \`permanent: true\` only for true platform boundaries" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "See TESTING.md 'No-Mock Policy' for details." >> "$AUDIT_REPORT"
echo "status=fail" >> "$GITHUB_OUTPUT"
exit 1
fi
echo "## Status: ✅ PASS" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "**Total patterns found:** $VIOLATION_COUNT" >> "$AUDIT_REPORT"
echo "**All patterns allowlisted:** Yes" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "### Allowlist Summary" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
jq -r '.entries[] | "- `\(.path)`: \(.pattern)` — \(.rationale)"' "$ALLOWLIST_FILE" >> "$AUDIT_REPORT"
echo "status=pass" >> "$GITHUB_OUTPUT"
- name: Upload audit report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: no-mock-audit
path: test-results/no_mock_ci_audit.md
retention-days: 30
# Rust linting and formatting
lint:
name: Lint
needs: [no-mock-audit]
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
# Explicit feature list excludes `strict-path-dep-validation`, which is
# intentionally opt-in and requires specific sibling-repo git revisions
# (see build.rs CONTRACTS). `--all-features` would activate it and make
# the build fail because CI clones siblings at HEAD, not pinned revs.
run: cargo clippy --all-targets --features "qr encryption backtrace" -- -D warnings
# Rust unit tests
test-rust:
name: Rust Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run tests
# Explicit feature list excludes `strict-path-dep-validation` — see the
# Lint job for rationale.
run: cargo test --features "qr encryption backtrace" --verbose -- --nocapture
env:
RUST_LOG: debug
- name: Run doc tests
run: cargo test --doc
- name: Run Rust E2E tests with JSONL logging
shell: bash
run: |
set -euo pipefail
mapfile -t tests < <(git ls-files 'tests/e2e_*.rs' | sed 's#^tests/##; s#\\.rs$##')
if [[ "${#tests[@]}" -eq 0 ]]; then
echo "No e2e_* tests found; skipping."
exit 0
fi
args=()
for t in "${tests[@]}"; do
args+=(--test "$t")
done
# Explicit feature list excludes `strict-path-dep-validation` — see
# the Lint job for rationale.
E2E_LOG=1 cargo test --features "qr encryption backtrace" --verbose "${args[@]}" -- --nocapture
- name: Validate E2E JSONL logs
if: always()
shell: bash
run: |
if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then
./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl
else
echo "No E2E JSONL logs found to validate"
fi
- name: Upload E2E JSONL logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-jsonl-${{ matrix.os }}
path: test-results/e2e/*.jsonl
if-no-files-found: ignore
retention-days: 14
ssh-sync-docker:
name: SSH Sync Docker Tests
needs: [no-mock-audit]
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install SSH sync tools
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y openssh-client rsync
docker version
docker info
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run SSH sync integration tests
run: cargo test --features "qr encryption backtrace" --test ssh_sync_integration -- --ignored --test-threads=1 --nocapture
env:
RUST_LOG: debug
- name: Run SSH sources E2E tests
run: E2E_LOG=1 cargo test --features "qr encryption backtrace" --test e2e_ssh_sources -- --ignored --test-threads=1 --nocapture
env:
RUST_LOG: debug
- name: Validate SSH E2E JSONL logs
if: always()
shell: bash
run: |
if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then
./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl
else
echo "No SSH E2E JSONL logs found to validate"
fi
- name: Upload SSH E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: ssh-e2e-jsonl
path: test-results/e2e/*.jsonl
if-no-files-found: ignore
retention-days: 14
e2e-orchestrated:
name: E2E Orchestrator (Rust + Shell)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run orchestrated E2E runner (Rust + Shell)
shell: bash
run: |
set -euo pipefail
RUN_PLAYWRIGHT=0 E2E_LOG=1 ./scripts/tests/run_all.sh
- name: Validate E2E JSONL logs
if: always()
shell: bash
run: |
if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then
./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl
else
echo "No E2E JSONL logs found to validate"
fi
- name: Show E2E summary
if: always()
shell: bash
run: |
if [[ -f "test-results/e2e/summary.md" ]]; then
cat test-results/e2e/summary.md
else
echo "No summary.md found"
fi
- name: Upload orchestrated E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-orchestrated-logs
path: |
test-results/e2e/combined.jsonl
test-results/e2e/summary.md
test-results/e2e/*.jsonl
if-no-files-found: ignore
retention-days: 14
# TUI E2E matrix: themes × degradation × breakpoints (2dccg.11.4)
e2e-tui-matrix:
name: TUI E2E Matrix
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run TUI stress and E2E scenario tests
run: |
set -euo pipefail
cargo test --lib 'stress_' -- --nocapture 2>&1 | tee test-results-stress.txt
cargo test --lib 'e2e_scenario' -- --nocapture 2>&1 | tee test-results-e2e.txt
cargo test --lib 'cross_theme_degradation' -- --nocapture 2>&1 | tee test-results-matrix.txt
cargo test --lib 'rendering_token_affordance' -- --nocapture 2>&1 | tee test-results-affordance.txt
cargo test --lib 'density_' -- --nocapture 2>&1 | tee test-results-density.txt
env:
RUST_BACKTRACE: 1
- name: Generate TUI matrix summary
if: always()
shell: bash
run: |
set -euo pipefail
mkdir -p test-results/tui-matrix
{
echo "# TUI E2E Matrix Summary"
echo ""
echo "**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "**Commit:** ${{ github.sha }}"
echo ""
echo "## Test Categories"
echo ""
for f in test-results-*.txt; do
category=$(echo "$f" | sed 's/test-results-//; s/\.txt//')
passed=$(grep -c "^test .* ok$" "$f" 2>/dev/null || echo 0)
failed=$(grep -c "^test .* FAILED$" "$f" 2>/dev/null || echo 0)
echo "- **${category}**: ${passed} passed, ${failed} failed"
done
} > test-results/tui-matrix/summary.md
cat test-results/tui-matrix/summary.md
- name: Upload TUI matrix artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: tui-e2e-matrix
path: |
test-results-*.txt
test-results/tui-matrix/summary.md
retention-days: 14
# Crypto test vectors
crypto-vectors:
name: Crypto Test Vectors
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run crypto vector tests
run: cargo test --test crypto_vectors -- --nocapture
env:
RUST_LOG: debug
# Security audit
security:
name: Security Audit
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Install cargo-audit
uses: taiki-e/install-action@878643b9fbcb563eeb35c8d9abe2ea9c84cb55bb # cargo-audit
- name: Run cargo audit
run: cargo audit
# Build artifacts (verification only, not for release)
build:
name: Build (${{ matrix.target }})
needs: [lint, test-rust, ssh-sync-docker, crypto-vectors, security]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-15-intel
target: x86_64-apple-darwin
- os: macos-14
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Build release
run: cargo build --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: cass-${{ matrix.target }}
path: target/${{ matrix.target }}/release/cass*
e2e-log-summary:
name: E2E Log Summary
needs: [test-rust]
runs-on: ubuntu-latest
if: always()
timeout-minutes: 10
permissions:
contents: read
pull-requests: write
steps:
- name: Download E2E log artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
continue-on-error: true
with:
pattern: 'e2e-jsonl-*'
path: artifacts
merge-multiple: true
- name: Aggregate E2E JSONL logs
shell: bash
run: |
set -euo pipefail
mkdir -p artifacts test-results/e2e
find artifacts -type f -name "*.jsonl" -print0 | sort -z | xargs -0 cat > test-results/e2e/combined.jsonl || true
- name: Generate E2E summary report
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json
from datetime import datetime, timezone
from pathlib import Path
combined_path = Path("test-results/e2e/combined.jsonl")
summary_path = Path("test-results/e2e/summary.md")
summary_path.parent.mkdir(parents=True, exist_ok=True)
total = passed = failed = skipped = flaky = 0
durations = {}
failures = []
if combined_path.exists():
for line in combined_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if event.get("event") != "test_end":
continue
runner = event.get("runner", "unknown")
result = event.get("result", {})
status = result.get("status", "unknown")
duration_ms = result.get("duration_ms", 0)
durations[runner] = durations.get(runner, 0) + int(duration_ms or 0)
total += 1
if status == "pass":
passed += 1
elif status == "skip":
skipped += 1
else:
failed += 1
retries = result.get("retries")
if status == "pass" and retries and int(retries) > 0:
flaky += 1
if status == "fail":
test = event.get("test", {})
error = event.get("error", {})
failures.append({
"runner": runner,
"suite": test.get("suite", "unknown"),
"name": test.get("name", "unknown"),
"file": test.get("file"),
"line": test.get("line"),
"message": error.get("message", "unknown error"),
})
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
lines = [
"# E2E Log Summary (CI)",
"",
f"**Generated:** {now}",
f"**Combined Log:** {combined_path.as_posix()}",
"",
"## Totals",
"",
f"- **Total Tests:** {total}",
f"- **Passed:** {passed}",
f"- **Failed:** {failed}",
f"- **Skipped:** {skipped}",
f"- **Flaky (passed on retry):** {flaky}",
"",
"## Duration by Runner",
"",
"| Runner | Duration (ms) |",
"|--------|---------------|",
]
if durations:
for runner, duration in sorted(durations.items()):
lines.append(f"| {runner} | {duration} |")
else:
lines.append("| (none) | 0 |")
lines.append("")
lines.append("## Failed Tests")
lines.append("")
if failures:
for f in failures:
location = ""
if f.get("file"):
if f.get("line"):
location = f"{f['file']}:{f['line']}"
else:
location = f"{f['file']}"
detail = f"{f['runner']} :: {f['suite']} :: {f['name']}"
if location:
detail += f" ({location})"
detail += f" — {f['message']}"
lines.append(f"- {detail}")
else:
lines.append("- None")
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Wrote {summary_path}")
PY
- name: Upload aggregated E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-log-summary
path: |
test-results/e2e/combined.jsonl
test-results/e2e/summary.md
retention-days: 14
if-no-files-found: ignore
- name: Comment summary on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const fs = require('fs');
const marker = '<!-- cass-e2e-summary -->';
const rustStart = '<!-- cass-e2e-rust:start -->';
const rustEnd = '<!-- cass-e2e-rust:end -->';
let summary = fs.readFileSync('test-results/e2e/summary.md', 'utf8');
if (summary.startsWith('# ')) {
summary = summary.replace(/^#\s+.*$/m, '## Rust E2E Summary');
} else if (!summary.startsWith('## ')) {
summary = `## Rust E2E Summary\n\n${summary}`;
}
const rustSection = `${rustStart}\n${summary.trim()}\n${rustEnd}`;
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find(comment => comment.body.includes(marker));
const upsertSection = (body, section) => {
if (body.includes(rustStart) && body.includes(rustEnd)) {
const regex = new RegExp(`${rustStart}[\\s\\S]*?${rustEnd}`, 'm');
return body.replace(regex, section);
}
return `${body.trim()}\n\n${section}`;
};
let body = existing ? existing.body : marker;
if (!body.includes(marker)) {
body = `${marker}\n${body}`;
}
body = upsertSection(body, rustSection);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}