Skip to content

ci: shard zig-tsan + zig-coverage 5-way; fix TSan never actually running#5

Merged
cuzzo merged 1 commit intomasterfrom
ci-shard-tsan-coverage
May 3, 2026
Merged

ci: shard zig-tsan + zig-coverage 5-way; fix TSan never actually running#5
cuzzo merged 1 commit intomasterfrom
ci-shard-tsan-coverage

Conversation

@cuzzo
Copy link
Copy Markdown
Owner

@cuzzo cuzzo commented May 3, 2026

The single zig-tsan and zig-coverage CI jobs were hitting their timeout ceilings (60min and 30min respectively). While verifying the sharding fix, found that the TSan job was never actually instrumenting tests -- zig build test -Dsanitize-thread flowed the flag through to unit_tests built in stage2 + Debug, which silently drops the TSan runtime. The actually-instrumented path (test-tsan step, LLVM + sanitize_thread) was never invoked from CI.

Changes:

  • build.zig: add -Dshard-index / -Dshard-count options. Round-robin filter applied to both test_step (used by coverage) and test_tsan_step (used by the new TSan job). Defaults (1, 0) preserve unsharded local behavior.
  • build.zig: switch TSan optimize from ReleaseSafe to Debug. TSan needs LLVM backend, not ReleaseSafe specifically -- the original comment conflated the two. Debug + LLVM + TSan compiles ~4x faster (3s vs 15s/test) and yields more instrumentation symbols than ReleaseSafe (which optimized some away).
  • ci.yml: zig-tsan switched from zig build test -Dsanitize-thread to zig build test-tsan so real TSan runs. Matrix-sharded 5-way; per- shard timeout 30min (measured ~7s locally for shard 0 with 9 tests).
  • ci.yml: zig-coverage matrix-sharded 5-way. Each shard uploads its own cobertura.xml; Codecov server-side merges via shared flags: zig.

The single zig-tsan and zig-coverage CI jobs were hitting their timeout
ceilings (60min and 30min respectively). While verifying the sharding
fix, found that the TSan job was never actually instrumenting tests --
`zig build test -Dsanitize-thread` flowed the flag through to unit_tests
built in stage2 + Debug, which silently drops the TSan runtime. The
actually-instrumented path (`test-tsan` step, LLVM + sanitize_thread)
was never invoked from CI.

Changes:
- build.zig: add `-Dshard-index` / `-Dshard-count` options. Round-robin
  filter applied to both `test_step` (used by coverage) and
  `test_tsan_step` (used by the new TSan job). Defaults (1, 0) preserve
  unsharded local behavior.
- build.zig: switch TSan optimize from ReleaseSafe to Debug. TSan needs
  LLVM backend, not ReleaseSafe specifically -- the original comment
  conflated the two. Debug + LLVM + TSan compiles ~4x faster (3s vs
  15s/test) and yields more instrumentation symbols than ReleaseSafe
  (which optimized some away).
- ci.yml: zig-tsan switched from `zig build test -Dsanitize-thread` to
  `zig build test-tsan` so real TSan runs. Matrix-sharded 5-way; per-
  shard timeout 30min (measured ~7s locally for shard 0 with 9 tests).
- ci.yml: zig-coverage matrix-sharded 5-way. Each shard uploads its own
  cobertura.xml; Codecov server-side merges via shared `flags: zig`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 3, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.70%. Comparing base (90607b2) to head (85d9d7a).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master       #5      +/-   ##
==========================================
- Coverage   83.94%   83.70%   -0.25%     
==========================================
  Files          51       58       +7     
  Lines       19505    19850     +345     
  Branches     8340     8340              
==========================================
+ Hits        16374    16615     +241     
- Misses       3131     3235     +104     
Flag Coverage Δ
ruby 83.94% <ø> (ø)
zig 69.85% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cuzzo cuzzo merged commit 735179a into master May 3, 2026
11 of 16 checks passed
cuzzo added a commit that referenced this pull request May 7, 2026
Self-host prep tracker task #10 (P1, Sorbet/RBS gradual typing). Adds:

  - Gemfile gems: sorbet, sorbet-runtime, tapioca (development group)
  - sorbet/config: --dir . with vendor/, zig*/, spec/, transpile-tests/,
    examples/, benchmarks/, tmp/, docs/ ignored
  - sorbet/rbi/clear-stubs.rbi: hand-written Token stub to silence the
    one default-typed-false name-resolution false positive in
    parser.rb:63 (Sorbet suggested Socket)
  - .gitignore allow-list entry for /sorbet/
  - docs/agents/sorbet-string-symbol-workflow.md: how Sorbet drives the
    String/Symbol normalization sweep (tracker task #1) -- declare
    T.any(String, Symbol) on a method, tighten to one type, run
    `srb tc`, fix every flagged caller

Status:

  $ bundle exec srb tc
  No errors! Great job.

Why this lands earlier than its dependency order in the tracker
suggested: Sorbet is the *driver* for tasks #1, #2, #5, #9, #13, not a
follow-up to them. Installing it at default `# typed: false` does
nothing on its own (no enforcement, no perf cost), but unlocks the
type-narrowing workflow needed to land String/Symbol normalization
without grep-and-pray. Files flip to `# typed: true` then
`# typed: strict` per the per-file gate (task #20) as cleanup lands.

Known incompat: tapioca 0.19 + Ruby 3.2 + sorbet-runtime 0.6.13196
crashes on `tapioca init` in `coerce_and_check_module_types`. Doesn't
block the static checker; hand-written stubs cover the small surface
area while we're at default typed:false. Workflow doc tracks the
upstream fix.

Verified:
  bundle exec prspec spec/         3599 examples, 0 failures
  ./clear test transpile-tests/    432 passed, 0 leaks
  bundle exec srb tc               No errors
cuzzo added a commit that referenced this pull request May 7, 2026
… fixes

Quick-fix menu support. The 9 Tier 1 + 2 fixable findings (and
anything we add later) now appear as LSP CodeActions when the user
triggers `:lua vim.lsp.buf.code_action()` (or whatever keybinding
their editor maps).

## What's new

- **`src/lsp/code_actions.rb`** — converts cached FixableFindings into
  LSP CodeActions. For each finding whose token range overlaps the
  client-requested range, we emit one CodeAction per `Fix`:

      :auto fixes        → kind="quickfix", isPreferred=true
      :interactive fixes → kind="refactor", no isPreferred
      Each Fix.edits     → TextEdits with range computed via Position

  Fix.description becomes the action title; the originating
  Diagnostic is attached so the client groups actions under the
  source error.

- **Server wiring** — new dispatch entry for
  `textDocument/codeAction`. The handler reads from
  `DocumentStore`'s cached findings (populated by the most recent
  `analyze_and_publish` pass), so codeAction is O(findings) — no
  re-analysis per request.

- **Capability advertisement** — `initialize` now declares
  `codeActionProvider: { codeActionKinds: ["quickfix", "refactor"] }`
  so clients know to surface our actions in their UI.

## Range overlap

Two ranges overlap unless one strictly ends before the other begins.
Touching boundaries (e.g. action ends at line 0 char 1, request
starts at line 0 char 1) count as overlapping — that matches Neovim's
default expand-from-cursor selection where the cursor sits on the
boundary character.

## Smoke test

    src = "FN main() RETURNS Void ->
             x = 5;
             WITH RESTRICT x { _ = x; }
           END"
    → didOpen + codeAction(line=2)
    ← {
        kind:        "quickfix",
        title:       "Declare 'x' as MUTABLE at its binding site (line 2).",
        isPreferred: true,
        edit:        { documentChanges: [{
                         textDocument: {uri, version: 1},
                         edits: [{
                           range:   { start: {line: 1, character: 2}, end: {…} },
                           newText: "MUTABLE "
                         }],
                       }] },
        diagnostics: [{ severity: 1, code: "WITH_RESTRICT_NEEDS_MUTABLE", … }],
      }

Editor users see "Declare 'x' as MUTABLE" in their quick-fix menu;
accepting the action inserts `MUTABLE ` at the binding site.

## Coverage

100% on src/lsp/ (358/358 lines). 91 LSP specs, 17 of which target
code_actions.rb directly:
  - empty-document / no-cache / no-fix paths
  - kind + isPreferred mapping for both confidences
  - title from Fix.description
  - originating Diagnostic attached
  - WorkspaceEdit shape (documentChanges → textDocument + edits)
  - multi-edit Fix expansion
  - range-overlap edge cases (touching, non-overlapping)
  - server-level integration: open + codeAction → expected response

Verified:
- 4171 Ruby specs (was 4154 + 17 new) — 0 failures
- 514 transpile-tests — 0 leaks
- module-integration / ffi-integration — green
- Smoke test: editor receives a quickfix with the correct edit.

## Next commit (per plan #5)

Hover support: `textDocument/hover` returns the registry summary +
cause + fix_hint plus the bad/good example from spec annotations,
rendered as markdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants