Skip to content

test(mcp-cli): convert non-boundary cases to in-process, cutting real spawns 78%#8631

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
tryeverything24:test-mcp-cli-spawn-reduction-8587
Jul 25, 2026
Merged

test(mcp-cli): convert non-boundary cases to in-process, cutting real spawns 78%#8631
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
tryeverything24:test-mcp-cli-spawn-reduction-8587

Conversation

@tryeverything24

Copy link
Copy Markdown
Contributor

Convert the subprocess-spawning test cases in the 11 in-scope mcp-cli-*.test.ts files to the existing in-process harness pattern (template: test/unit/mcp-cli-contributor-profile-inprocess.test.ts), per the closed keep/convert rule in the issue. Each converted case replaces its run()/runAsync()/StdioClientTransport subprocess spawn with the same assertions driven through the bin's exported runCli/maintainCli/server (dynamic import of the committed .ts source after env setup, relying on the existing isProcessEntrypoint guard — the guard itself is untouched). The subprocess version of every converted case is deleted; kept cases still spawn the real CLI.

Closes #8587

What changed (transport swap only — no scenario deleted, no .skip() added)

  • Each file now starts one fixture server + temp config dir in beforeAll, sets the module-load env (LOOPOVER_API_URL, LOOPOVER_CONFIG_DIR, …) before dynamically importing packages/loopover-mcp/bin/loopover-mcp.ts, and drives converted cases via captureStdout(() => mod.runCli([...])) (or InMemoryTransport.createLinkedPair() + mod.server for stdio-tool cases), exactly like the template. Per-test fixture variation uses a shared mutable options object (the harness reads options per request), and per-test tokens use call-time process.env reads.
  • mcp-cli-maintain-tools.test.ts's per-test new StdioClientTransport({ command: "node", args: [bin, "--stdio"] }) connection became InMemoryTransport + the exported server — no subprocess.
  • Kept-as-subprocess cases (rule letters from the issue): every runExpectingFailure exit-status/{ok:false}-envelope case (a); expect(() => run(...)).toThrow CLI-argv/unknown-command cases (a); version-banner cases (c); config-provenance / --profile-switching / config-path-override cases where the bin resolves the value at module load (c) — profiles' active-profile is a module-load const (bin lines ~365-368), so profile-switch visibility genuinely needs a fresh process; telemetry persistence-across-invocations cases (d) — telemetry status/doctor/config report the import-time config snapshot, so fresh-process re-read is the test's subject; doctor's "exits non-zero from doctor --exit-code when a check fails" (a — real process.exit wiring; its exit-code-0 sibling now asserts the code runCli returns in-process, per the handler's return ... ? 1 : 0).
  • Borderline case called out by the issue: maintain-tools' "rejects an unknown action class and an unknown autonomy level before any API call" asserts result.isError === true on the callTool result (structured tool error, not a thrown process exception) → converted under rule (f).
  • Untouched, per "Out of scope": mcp-discovery.test.ts, mcp-local-telemetry-chokepoint.test.ts, vitest.config.ts, .github/workflows/ci.yml, all pool/concurrency settings, test/unit/support/mcp-cli-harness.ts, and packages/** (the bin already exports runCli/server/maintainCli; no new exports were needed). The dynamic .ts import uses the same const-indirection as the template so tsc --noEmit stays clean (TS5097).

Spawn count before/after (grep -cE '\brun\(|\brunAsync\(', run on main and on this branch)

File before after
test/unit/mcp-cli-packets.test.ts 37 0
test/unit/mcp-cli-maintain.test.ts 53 0
test/unit/mcp-cli-doctor.test.ts 27 0
test/unit/mcp-cli-basics.test.ts 37 18 (17 kept sites + 1 comment mention)
test/unit/mcp-cli-profiles.test.ts 25 19
test/unit/mcp-cli-review-pr.test.ts 13 1
test/unit/mcp-cli-telemetry.test.ts 23 22
test/unit/mcp-cli-notifications.test.ts 17 0
test/unit/mcp-cli-pr-outcomes.test.ts 11 0
test/unit/mcp-cli-monitor-open-prs.test.ts 10 0
test/unit/mcp-cli-maintain-tools.test.ts (run) 1 0
test/unit/mcp-cli-maintain-tools.test.ts (grep -cE 'new StdioClientTransport') 1 (≈16 real spawns, one per test) 0
Total (grep metric) 254 60 (−76%)
Total real spawns (issue table incl. maintain-tools' ≈16) 269 60 (−78%)

Telemetry stays almost fully subprocess by design: its cases assert opt-in persistence across separate CLI invocations (rule d — the bin snapshots its config at module load, so an in-process version would assert nothing). Profiles keeps its --profile-switching family for the same module-load reason (rule c). Everything the rule allows was converted; the aggregate reduction is well past the 50% floor.

Required verification (run fresh on this machine, isolated 11-file runs, same command list as the issue)

BEFORE (clean origin/main worktree, npm ci + npx turbo run build --filter=@loopover/engine --filter=@loopover/mcp):

$ npx vitest run test/unit/mcp-cli-packets.test.ts test/unit/mcp-cli-maintain.test.ts test/unit/mcp-cli-doctor.test.ts test/unit/mcp-cli-basics.test.ts test/unit/mcp-cli-profiles.test.ts test/unit/mcp-cli-review-pr.test.ts test/unit/mcp-cli-telemetry.test.ts test/unit/mcp-cli-notifications.test.ts test/unit/mcp-cli-pr-outcomes.test.ts test/unit/mcp-cli-monitor-open-prs.test.ts test/unit/mcp-cli-maintain-tools.test.ts --reporter=verbose

 Test Files  11 passed (11)
      Tests  161 passed (161)
   Start at  18:30:42
   Duration  16.34s (transform 7.19s, setup 11.58s, import 866ms, tests 77.01s, environment 1ms)

AFTER (this branch, same command):

 Test Files  11 passed (11)
      Tests  161 passed (161)
   Start at  18:50:20
   Duration  15.23s (transform 12.05s, setup 11.34s, import 994ms, tests 36.67s, environment 1ms)

Cumulative "tests" time for the 11 files: 77.01s → 36.67s (−52%). Wall Duration moves less because it is floored by two deliberately kept cases: profiles' #6792 device-flow 429 test (~10s of real poll-interval waiting, unrelated to spawn cost) and the kept startup-semantics subprocess cases.

Per-file breakdown (sum of the verbose reporter's per-test times in the two runs above):

File tests before after
mcp-cli-packets.test.ts 22 9.44s 0.64s
mcp-cli-maintain.test.ts 25 9.63s 0.13s
mcp-cli-doctor.test.ts 19 10.14s 4.72s
mcp-cli-basics.test.ts 21 8.06s 4.08s
mcp-cli-profiles.test.ts 8 15.10s 13.25s
mcp-cli-review-pr.test.ts 11 3.60s 0.79s
mcp-cli-telemetry.test.ts 6 4.68s 3.76s
mcp-cli-notifications.test.ts 12 3.57s 0.41s
mcp-cli-pr-outcomes.test.ts 10 4.67s 0.60s
mcp-cli-monitor-open-prs.test.ts 8 3.70s 0.27s
mcp-cli-maintain-tools.test.ts 19 4.41s 0.14s

Coverage / hygiene

  • 161 test scenarios before, 161 after, zero skips; every converted case keeps its original assertions (transport swap only — several converted cases gained an extra returned-exit-code assertion, none lost any).
  • npm run typecheck (tsc --noEmit) green; git diff --check clean; no new files (no -inprocess bolt-ons — converted cases live in their original files next to the kept subprocess cases, matching maintain-tools' existing mixed structure).
  • test/** is outside Codecov's patch gate per the issue's non-goals; the changed files are all under test/unit/.

…s pattern (JSONbored#8587)

Convert every run()/runAsync()/StdioClientTransport test case in the 11
mcp-cli-*.test.ts files that does not need the real process boundary to
the existing InMemoryTransport/runCli in-process pattern (template:
mcp-cli-contributor-profile-inprocess.test.ts), deleting the subprocess
version of each converted case. Cases asserting process exit behavior,
startup/module-load env resolution, or cross-invocation process
isolation are kept as real subprocess spawns per the issue rule.

Real subprocess spawn count across the 11 files drops 254 -> 60
(grep -cE ., -76%); vitest cumulative tests time for the same files
drops 77.01s -> 36.67s. All 161 test scenarios preserved, zero skips.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 25, 2026
@loopover-orb

loopover-orb Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-25 15:59:31 UTC

11 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR converts most subprocess-spawning tests in the mcp-cli-*.test.ts suite to in-process execution via dynamic import of the bin's exported runCli/server, following an existing template pattern. The conversion is mechanical but careful: each file correctly moves module-load-sensitive env vars (LOOPOVER_API_URL, LOOPOVER_CONFIG_DIR) before the dynamic import, uses call-time env mutation via withEnv for per-test variation, and explicitly documents which cases are kept as real subprocesses (exit-code/argv-parsing/version-banner cases) with clear rationale. The doctor test's per-test shared-config-dir cleanup (deleting config.json in afterEach) and the maintain-tools test's InMemoryTransport swap for StdioClientTransport both look correct and preserve the original assertions.

Nits — 6 non-blocking
  • test/unit/mcp-cli-maintain.test.ts:24-27 still reads MAINTAIN_AUTONOMY_LEVELS from `packages/loopover-mcp/dist/bin/loopover-mcp.js` (compiled dist) while every other assertion in this file now runs against the freshly-imported `.ts` source — worth a comment on why this one drift-check intentionally still targets dist, or switching it to parse the `.ts` source for consistency.
  • The external brief flags a possible generic_secret_assignment in test/unit/mcp-cli-packets.test.ts:259 and mcp-cli-profiles.test.ts:75 — likely just a hardcoded fixture token like "session-token" reused from the existing harness, but worth a quick eyeball since those files weren't in the full-content sample here.
  • test/unit/mcp-cli-doctor.test.ts's `withEnv` helper and `captureStdout` helper are duplicated near-verbatim across mcp-cli-doctor.test.ts, mcp-cli-maintain.test.ts, mcp-cli-maintain-tools.test.ts, and mcp-cli-monitor-open-prs.test.ts — consider hoisting into the shared harness now that 4+ files need identical stdout-capture/env-scoping logic.
  • Consider factoring `captureStdout` and `withEnv` into test/unit/support/mcp-cli-harness.ts since the pattern is now repeated across at least four converted files.
  • Double check the two flagged lines in mcp-cli-packets.test.ts and mcp-cli-profiles.test.ts aren't accidentally embedding a real credential rather than a fixture placeholder.
  • Possible secret-shaped assignment in the diff (generic_secret_assignment) — Verify the value is not a real credential.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #8587
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 86 registered-repo PR(s), 40 merged, 4 issue(s).
Contributor context ✅ Confirmed Gittensor contributor tryeverything24; Gittensor profile; 86 PR(s), 4 issue(s).
Improvement ℹ️ Insufficient signal risk: clean · value: insufficient-signal · LLM: moderate
Linked issue satisfaction

Addressed
The PR converts subprocess-spawning tests in mcp-cli-doctor.test.ts and mcp-cli-maintain.test.ts (shown in the diff) to the in-process harness pattern the issue prescribes, and its description states the same conversion was applied across all 11 in-scope files while explicitly preserving process-boundary-dependent cases per the issue's keep/convert rule, closing #8587.

Review context
  • Author: tryeverything24
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: JavaScript, TypeScript, Python, HTML, C++, Java, PHP, C#
  • Official Gittensor activity: 86 PR(s), 4 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit cae09b4 into JSONbored:main Jul 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reduce mcp-cli-*.test.ts real-subprocess spawn count (currently ~40% of suite time)

1 participant