mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
* fix(tests): eliminate env mutex poison cascade and fix test flakiness The shared ENV_MUTEX used by ~68 config tests would cascade a single test panic into failures across every module. Replace all .unwrap() / .expect() lock acquisitions with a poison-recovering lock_env() helper. Consolidate rogue module-local ENV_LOCK instances (workspace, orchestrator, bootstrap) onto the shared global mutex to prevent cross-module races. Also fixes: - gateway user_id fallback was hardcoded to "default" instead of owner_id - test_ironclaw_env_path used LazyLock which is order-dependent Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test(helpers): add regression test for lock_env poison recovery Satisfies the regression-test-check CI gate by adding a test that intentionally poisons ENV_MUTEX and verifies lock_env() recovers. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(ci): detect test changes inside #[cfg(test)] regions The regression test check relied on git diff -W to expand context to function boundaries, but git doesn't recognize Rust `mod tests {}` as a function boundary. Changes to imports, helpers, or lock calls inside test modules were invisible to the check. Add a line-level fallback: for each changed .rs file, find where #[cfg(test)] starts and check if any diff hunk targets a line at or after that boundary. This catches edits anywhere inside test modules regardless of git's language awareness. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address PR review feedback - Clear ENV_MUTEX poison after regression test so it doesn't leave global state dirty for subsequent tests. - Fix CI regression-test-check to match #[cfg(test)] only when followed by `mod` (the test module pattern), avoiding false positives from standalone #[cfg(test)] items like statics or functions. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
185 lines
7.4 KiB
YAML
185 lines
7.4 KiB
YAML
name: Regression Test Check
|
|
|
|
on:
|
|
pull_request:
|
|
|
|
jobs:
|
|
regression-test:
|
|
name: Regression test enforcement
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Fetch PR head and base
|
|
run: |
|
|
git fetch origin ${{ github.event.pull_request.base.ref }}
|
|
git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-head
|
|
|
|
- name: Check for regression tests
|
|
env:
|
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
BASE_REF="origin/${{ github.event.pull_request.base.ref }}"
|
|
# Use the actual PR head, not the merge commit that actions/checkout checks out
|
|
HEAD_REF="pr-head"
|
|
|
|
# --- 1. Is this a fix PR? Check title first, then commit messages ---
|
|
IS_FIX=false
|
|
|
|
if grep -qiE '^(fix(\(.*\))?|hotfix|bugfix):' <<< "$PR_TITLE"; then
|
|
IS_FIX=true
|
|
fi
|
|
|
|
if [ "$IS_FIX" = false ]; then
|
|
COMMITS=$(git log --format='%s' "${BASE_REF}..${HEAD_REF}")
|
|
if grep -qiE '^(fix(\(.*\))?|hotfix|bugfix):' <<< "$COMMITS"; then
|
|
IS_FIX=true
|
|
fi
|
|
fi
|
|
|
|
# --- 1b. Does this PR touch high-risk state machine or resilience code? ---
|
|
CHANGED_FILES=$(git diff --name-only "${BASE_REF}...${HEAD_REF}")
|
|
|
|
TOUCHES_HIGH_RISK=false
|
|
HIGH_RISK_PATTERNS=(
|
|
"src/context/state.rs"
|
|
"src/agent/session.rs"
|
|
"src/llm/circuit_breaker.rs"
|
|
"src/llm/retry.rs"
|
|
"src/llm/failover.rs"
|
|
"src/agent/self_repair.rs"
|
|
"src/agent/agentic_loop.rs"
|
|
"src/tools/execute.rs"
|
|
"crates/ironclaw_safety/src/"
|
|
)
|
|
|
|
for pattern in "${HIGH_RISK_PATTERNS[@]}"; do
|
|
if echo "$CHANGED_FILES" | grep -q "$pattern"; then
|
|
TOUCHES_HIGH_RISK=true
|
|
echo "High-risk file matched: $pattern"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Skip only if NEITHER condition holds — no double-firing on fix PRs
|
|
if [ "$IS_FIX" = false ] && [ "$TOUCHES_HIGH_RISK" = false ]; then
|
|
echo "Not a fix PR and no high-risk files changed — skipping."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$IS_FIX" = true ]; then
|
|
echo "Fix PR detected."
|
|
fi
|
|
if [ "$TOUCHES_HIGH_RISK" = true ]; then
|
|
echo "High-risk state machine or resilience code modified."
|
|
fi
|
|
|
|
# --- 2. Skip label or commit message marker ---
|
|
if grep -qF ',skip-regression-check,' <<< ",$PR_LABELS,"; then
|
|
echo "skip-regression-check label present — skipping."
|
|
exit 0
|
|
fi
|
|
|
|
COMMIT_BODIES=$(git log --format='%B' "${BASE_REF}..${HEAD_REF}")
|
|
if grep -qF '[skip-regression-check]' <<< "$COMMIT_BODIES"; then
|
|
echo "[skip-regression-check] found in commit message — skipping."
|
|
exit 0
|
|
fi
|
|
|
|
# --- 3. Exempt static-only / docs-only changes ---
|
|
if [ -z "$CHANGED_FILES" ]; then
|
|
echo "No changed files — skipping."
|
|
exit 0
|
|
fi
|
|
|
|
ALL_EXEMPT=true
|
|
while IFS= read -r file; do
|
|
case "$file" in
|
|
src/channels/web/static/*) ;;
|
|
*.md) ;;
|
|
*) ALL_EXEMPT=false; break ;;
|
|
esac
|
|
done <<< "$CHANGED_FILES"
|
|
|
|
if [ "$ALL_EXEMPT" = true ]; then
|
|
echo "All changes are static assets or docs — skipping."
|
|
exit 0
|
|
fi
|
|
|
|
# --- 4. Look for test changes ---
|
|
|
|
# Fast path: new test attributes or test modules in added lines.
|
|
if git diff "${BASE_REF}...${HEAD_REF}" -U0 -- '*.rs' | grep -qE '^\+.*(#\[test\]|#\[tokio::test\]|#\[cfg\(test\)\]|mod tests)'; then
|
|
echo "Test changes found in .rs files."
|
|
exit 0
|
|
fi
|
|
|
|
# Whole-function context: detect edits inside existing test functions.
|
|
# Uses -W (whole function) which works when git recognises function boundaries.
|
|
if git diff "${BASE_REF}...${HEAD_REF}" -W -- '*.rs' | awk '
|
|
/^@@/ { if (has_test && has_add) { found=1; exit } has_test=0; has_add=0 }
|
|
/^ .*#\[test\]/ || /^ .*#\[tokio::test\]/ || /^ .*#\[cfg\(test\)\]/ || /^ .*mod tests/ { has_test=1 }
|
|
/^\+.*#\[test\]/ || /^\+.*#\[tokio::test\]/ || /^\+.*#\[cfg\(test\)\]/ || /^\+.*mod tests/ { has_test=1 }
|
|
/^\+[^+]/ { has_add=1 }
|
|
END { if (has_test && has_add) found=1; exit !found }
|
|
'; then
|
|
echo "Test changes found in existing test functions."
|
|
exit 0
|
|
fi
|
|
|
|
# Line-level check: detect changes inside #[cfg(test)] mod blocks.
|
|
# git -W relies on function boundary detection which misses Rust mod blocks,
|
|
# so this fallback checks whether changed line numbers fall within test modules.
|
|
# We specifically match #[cfg(test)] that is followed by `mod` (same or next
|
|
# line) to avoid false positives from standalone #[cfg(test)] items like
|
|
# individual statics or functions.
|
|
CHANGED_RS=$(echo "$CHANGED_FILES" | grep '\.rs$' || true)
|
|
if [ -n "$CHANGED_RS" ]; then
|
|
while IFS= read -r rs_file; do
|
|
[ -f "$rs_file" ] || continue
|
|
|
|
# Find the line where #[cfg(test)] precedes a `mod` declaration.
|
|
# Handles both `#[cfg(test)] mod tests` (same line) and the two-line form.
|
|
TEST_MOD_START=$(awk '
|
|
/^[[:space:]]*#\[cfg\(test\)\].*mod / { print NR; exit }
|
|
/^[[:space:]]*#\[cfg\(test\)\][[:space:]]*$/ { pending=NR; next }
|
|
pending && /^[[:space:]]*mod / { print pending; exit }
|
|
{ pending=0 }
|
|
' "$rs_file")
|
|
[ -n "$TEST_MOD_START" ] || continue
|
|
|
|
# Get changed line numbers in this file from the diff hunk headers.
|
|
# Each @@ line looks like: @@ -old,count +new,count @@
|
|
while IFS= read -r hunk_line; do
|
|
line_no=$(echo "$hunk_line" | sed -E 's/^@@ -[0-9,]+ \+([0-9]+).*/\1/')
|
|
[ -n "$line_no" ] || continue
|
|
if [ "$line_no" -ge "$TEST_MOD_START" ]; then
|
|
echo "Test changes found: $rs_file has changes at line $line_no inside #[cfg(test)] mod block (starts at line $TEST_MOD_START)."
|
|
exit 0
|
|
fi
|
|
done < <(git diff "${BASE_REF}...${HEAD_REF}" -U0 -- "$rs_file" | grep -E '^@@')
|
|
done <<< "$CHANGED_RS"
|
|
fi
|
|
|
|
if grep -qE '^tests/' <<< "$CHANGED_FILES"; then
|
|
echo "Test file changes found under tests/."
|
|
exit 0
|
|
fi
|
|
|
|
# --- 5. No tests found ---
|
|
if [ "$IS_FIX" = true ]; then
|
|
echo "::warning::This PR looks like a bug fix but contains no test changes."
|
|
fi
|
|
if [ "$TOUCHES_HIGH_RISK" = true ]; then
|
|
echo "::warning::This PR modifies high-risk state machine or resilience code but includes no test changes."
|
|
fi
|
|
echo "::warning::Please add tests exercising the changed behavior, or apply the 'skip-regression-check' label if not feasible."
|
|
exit 1
|
|
|