Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Add a ProcessManager to HarnessContext to make subprocess (adb/xcrun/…) access testable

未关闭
#166 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
38/100
Issue 类型
功能
描述清晰度
基本清楚
活跃度
冷清

调研方向

先阅读 issue #165 中依赖的 HarnessContext 和 AsyncLocalStorage 工作,然后检查当前的 spawn wrapper 和 packages/platform-android/src/adb.ts:79。在定义 manager 和 fake 之前,追踪 subprocess 调用点以及现有的 simctl、adb 和 shared-prefs 测试。完成标准是调用点使用 getProcessManager().spawn,移除原始 child_process escape hatch 和独立的 spawn 导出,并且迁移后的测试使用符合 Subprocess 形状的 fake。

由索引模型根据 Issue 内容生成。

描述

enhancement

Problem Statement

All subprocess execution already funnels through a single spawn() in @react-native-harness/tools (a wrapper over nano-spawn) — with one exception: a raw node:child_process call at packages/platform-android/src/adb.ts:79.

The heaviest, most side-effecting host code shells out to external tools: adb (~21 call sites), xcrun simctl/devicectl (15+), xcodebuild, kepler, lipo/plutil/uname, and bash/unzip. Today it is tested with vi.spyOn(tools, 'spawn'), asserting on exact argv arrays and returning hand-cast fake result shapes. That approach:

  • is module-global monkeypatching, so it is not parallel-safe;
  • is brittle — it couples tests to exact CLI flag formatting;
  • makes the gnarliest, most externally-dependent code the hardest to test.

Solution

Extend the HarnessContext introduced in the companion FileSystem issue (#165) with a process manager, injected via the same AsyncLocalStorage, and exposed through an explicit getProcessManager() accessor that mirrors getFs().

The process manager owns spawn as a method. Subprocess call sites move from the bare spawn(...) to getProcessManager().spawn(...):

// before
await spawn('xcrun', ['simctl', 'boot', udid]);
// after
await getProcessManager().spawn('xcrun', ['simctl', 'boot', udid]);

This is a deliberate design choice over a "magic" spawn() that silently reads the ambient context. Consistency with getFs() matters: the dependency-injection origin should be visible at the call site. A plain-looking spawn(...) that secretly resolves an ambient runner hides where the implementation comes from; getProcessManager().spawn(...) makes it obvious. We accept a mechanical spawn(...) → getProcessManager().spawn(...) sweep across the subprocess-using modules as the price of that clarity. Crucially, this still avoids threading a dependency object through every function signature — the accessor reads from AsyncLocalStorage internally — so the core low-churn benefit of the context is preserved.

Consequences:

  • The standalone spawn() export in tools is removed in favour of getProcessManager().spawn; the current wrapper behavior (default options, logging) moves onto the real process-manager implementation.
  • The raw node:child_process call at adb.ts:79 is routed through the manager, closing the last escape hatch.
  • The default-provider fallback (a real nano-spawn-backed manager when no context is active) keeps production and external consumers working outside a wrapped context.
  • Tests set a fake process manager via runWithHarnessContext, replacing vi.spyOn(tools, 'spawn') with a parallel-safe, per-run mechanism.

Fake design (independent of the DI mechanism):

  • The default fake is an argv-programmable process runner: match on command + args, return canned stdout/stderr/exit code. This alone is a large improvement over the status quo and covers the vast majority of cases.
  • The fake must return a value matching nano-spawn's Subprocess shape — a thenable that also supports streaming / async iteration and carries a handle to the child — so streaming callers such as streamLogs and device-log tails behave correctly. This fidelity cost exists under any DI mechanism.
  • If a specific suite later needs rich device-state simulation (installed apps, boot state, shell properties), a stateful process-runner fake that interprets commands into an in-memory model can be layered on the same seam. That is an optional escalation, not a blocker. Dedicated domain-level ports (Adb, Simctl) remain a possible future refinement but are out of scope here.

Expected outcome

  • HarnessContext carries a ProcessManager, exposed via getProcessManager(), with a real nano-spawn-backed default.
  • Subprocess call sites use getProcessManager().spawn(...); the standalone spawn() export is removed and the adb.ts:79 node:child_process escape hatch is closed.
  • An argv-programmable, Subprocess-shaped fake is available from the testing-only entry.
  • Subprocess tests (e.g. simctl, adb, shared-prefs) are migrated off vi.spyOn(tools, 'spawn') to the fake.

High-level implementation plan

  1. (Depends on the FileSystem issue (#165) HarnessContext + AsyncLocalStorage plumbing.) Define the ProcessManager type, add getProcessManager() with a real nano-spawn-backed default, and move the current spawn() wrapper behavior onto the real manager.
  2. Migrate subprocess call sites from spawn(...) to getProcessManager().spawn(...), remove the standalone spawn() export, and route the raw node:child_process call at adb.ts:79 through the manager.
  3. Build the argv-programmable fake with Subprocess-shaped return values in the testing-only entry.
  4. Migrate existing spawn-spy tests to the fake.
  5. Document how to build a stateful process-runner fake for suites that need device-state fidelity.
主要语言
TypeScript
星标
330
派生
17
平均合并
8 天 11 小时
30 天内合并 PR
2

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

callstackincubator/react-native-harness 的其他 Issue

查看 callstackincubator/react-native-harness 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。