◐ Off-By-One · answer catalog

go-test-ci-e2e-external-service-fatal

1 answer(s)godocker

maxiterations: 100 # BUG: builds EvalCap{…, TokenBudget:-1} → judge compaction loop

📦 Source in repository (JSON)

Answer

The reproduction module is at /tmp/forgejo-fix-demo (real go test runs below). The fix, applied to the real codebase:

1. Forgejo E2E: reachability failure is a skip, not a fatal

Before (red CI since 07-30): Setup(t) used t.Fatalf when the live Forgejo was unreachable, so CI — which has no Forgejo — failed every commit.

func (s *suite) Setup(t *testing.T) {
    t.Helper()
    s.baseURL = forgejoURL()
    resp, err := http.Get(s.baseURL + "/api/v1/version")
    if err != nil {
        t.Fatalf("Forgejo unreachable at %s: %v", s.baseURL, err) // ← red CI
    }
    ...
}

After: unreachable ⇒ t.Skipf with a clear message; the suite guard stays in the existing s.Setup(t); the probe requires a healthy 2xx from the version endpoint so unrelated services/unhealthy instances also skip rather than fail.

// Setup is the suite-level reachability guard. Unreachable Forgejo is an
// environment condition → skip (never fail), so CI with no Forgejo stays green.
func (s *suite) Setup(t *testing.T) {
    t.Helper()
    s.baseURL = forgejoURL()
    c := &http.Client{Timeout: 3 * time.Second}
    resp, err := c.Get(s.baseURL + "/api/v1/version")
    if err != nil {
        t.Skipf("Forgejo E2E skipped: live Forgejo unreachable at %s (CI has no Forgejo; "+
            "start one or set FORGEJO_URL, e.g. simulate CI with FORGEJO_URL=http://<ip-address>:1): %v",
            s.baseURL, err)
    }
    defer resp.Body.Close()
    // Dead port, unrelated service on the port, or unhealthy instance: all
    // non-2xx probes are environment conditions → skip, never fail.
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        t.Skipf("Forgejo E2E skipped: version probe at %s/api/v1/version returned HTTP %d; "+
            "expected 2xx from a healthy Forgejo (CI has no Forgejo)", s.baseURL, resp.StatusCode)
    }
    s.client = c
}

Each TestForgejoE2E* keeps the inverted -short guard (runs unless -short; the foreman gate runs without -short, so local E2E still runs there) and is guarded by the existing s.Setup(t) — one guard for the whole suite:

func TestForgejoE2ERepos(t *testing.T) {
    if testing.Short() {
        return // inverted -short guard: local E2E still runs in the foreman gate
    }
    s := &suite{}
    s.Setup(t) // unreachable Forgejo ⇒ skip whole suite (not fail)
    // ...repo lifecycle E2E body (create repo, push, etc.)
}

func TestForgejoE2EIssues(t *testing.T) {
    if testing.Short() {
        return
    }
    s := &suite{}
    s.Setup(t)
    // ...issue lifecycle E2E body
}

Simulate CI locally: FORGEJO_URL=http://<ip-address>:1 go test ./... — dead port reproduces "no Forgejo", and the result is SKIP (exit 0), not FAIL.

2. Pipeline tier2: max_iterations: -1 defers to evaluator section caps

Before — tier2 stage max_iterations: 100 built EvalCap{MaxIterations: 100, TokenBudget: -1}; the judge compaction loop received the -1 token budget directly, could never satisfy "size ≤ budget", and compacted forever:

# pipeline.yaml (tier2 stage)
stages:
  - name: tier2
    max_iterations: 100   # BUG: builds EvalCap{…, TokenBudget:-1} → judge compaction loop

After — stage max_iterations: -1 signals "no stage-level override", so the cap defers to the evaluator's section caps:

stages:
  - name: tier2
    max_iterations: -1    # FIX: defer to evaluator section caps

Plus a defensive guard in the cap builder so a stage-level iteration count can never inject a -1 token budget into the compaction path:

// buildEvalCap: maxIterations == -1 defers to evaluator section caps.
// Any other value forces a stage cap and must NOT smuggle TokenBudget -1
// into the judge compaction loop.
func buildEvalCap(maxIterations, sectionTokenCap int) EvalCap {
    if maxIterations < 0 {
        return EvalCap{MaxIterations: -1, TokenBudget: sectionTokenCap} // defer
    }
    return EvalCap{MaxIterations: maxIterations, TokenBudget: sectionTokenCap} // stage cap + real budget
}

Evidence & signatures

All runs are real `go test` executions against the reproduction module (`go 1.26.5`, stdlib only, `/tmp/forgejo-fix-demo`).

| # | Scenario | Command | Result |
|---|----------|---------|--------|
| 1 | **CI simulation** (dead port) | `FORGEJO_URL=http://<ip-address>:1 go test ./... -v -run Forgejo` | `--- SKIP: TestForgejoE2ERepos` / `--- SKIP: TestForgejoE2EIssues` with message *"live Forgejo unreachable at http://<ip-address>:1 (CI has no Forgejo; …): dial tcp <ip-address>:1: connect: connection refused"* → **PASS, exit 0** |
| 2 | **Edge case: unrelated service on the port** (found live in this env: `<ip-address>:3000` returns `404 {"error":"Route GET /api/v1/version not found"}`) | `go test ./... -v -run Forgejo` | Both tests **SKIP** with *"version probe … returned HTTP 404; expected 2xx from a healthy Forgejo"* → **exit 0** (a probe-only guard would have wrongly FAILED here — hardened to non-2xx ⇒ skip) |
| 3 | **Live instance** (fake Forgejo serving `/api/v1/version` → 200) | `FORGEJO_URL=http://<ip-address>:34567 go test ./... -v -run Forgejo` | Both tests **RUN and PASS**: *"ran against live instance at http://<ip-address>:34567"* → proves reachable ⇒ suite proceeds, guard is permissive not crippling |
| 4 | **Inverted `-short` guard preserved** | `go test ./... -short -v -run Forgejo` | Both tests **PASS** with empty bodies (early return); foreman gate without `-short` still runs E2E |
| 5 | **EvalCap fix** | `go test ./... -v -run 'EvalCap\|JudgeCompaction'` | `TestEvalCapDeferral` PASS (old config ⇒ `TokenBudget:-1`; fixed ⇒ `TokenBudget:128000`); `TestJudgeCompactionDivergesWithOldConfig` PASS — old config diverges, watchdog after 100001 rounds (**the compaction loop**); `TestJudgeCompactionConvergesWithFix` PASS — `max_iterations:-1` converges in 20 rounds |
| 6 | **Full suite, CI simulation** | `FORGEJO_URL=http://<ip-address>:1 go test ./...` | **exit 0** (all SKIP/PASS) — green CI with no Forgejo |

**Edge cases tested**: dead port (connection refused) → skip; live-but-unrelated service (HTTP 404 probe) → skip; healthy live instance → run; `-short` → early return; non-2xx (5xx/unhealthy) → skip; full-suite exit code is 0 when Forgejo is absent.

**Verification method**: `TestJudgeCompactionConvergesWithFix` / `TestJudgeCompactionDivergesWithOldConfig` assert the *contract* — with the old config the compaction loop cannot converge (watchdog), with `max_iterations:-1` it converges deterministically — plus `TestEvalCapDeferral` pins the cap semantics (`-1` ⇒ defer to section caps).

---
{"model": "deepseek-v4-flash", "problem_class": "go-test-ci-e2e-external-service-fatal", "result": "passed", "tests": 5}
Generated from the verified corpus · MIT licensedBack to the catalog