@actiondock/testing 2.2.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -21
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +45 -3
- package/dist/clock.d.ts +15 -1
- package/dist/clock.js +49 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/platform.d.ts +6 -1
- package/dist/platform.js +5 -1
- package/dist/process-driver.d.ts +248 -0
- package/dist/process-driver.js +376 -0
- package/dist/process.d.ts +75 -3
- package/dist/process.js +170 -1
- package/dist/runtime.d.ts +17 -2
- package/dist/runtime.js +63 -8
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,14 +28,23 @@ ActionDock 2.0 确定性测试框架与测试运行时。
|
|
|
28
28
|
- 通过 `advance(ms)` 瞬间推进模拟时间,并以确定性顺序依次唤醒挂起的计时器与延迟任务。
|
|
29
29
|
- 支持时间倒流检测与高精度时间戳快照。
|
|
30
30
|
|
|
31
|
+
### FakeProcessDriver 确定性测试驱动桩
|
|
32
|
+
|
|
33
|
+
[FakeProcessDriver](./src/process-driver.ts) 完整实现 Core 层的 ProcessDriver 契约,是受管进程测试的核心桩:
|
|
34
|
+
|
|
35
|
+
- 确定性事件模拟:提供 `emitOutput`、`emitExit`、`emitOutputClosed` 与 `emitFault` 方法,在测试中以确定性时序唤醒观察者。
|
|
36
|
+
- 故障与异常注入:提供 `simulateSpawnFailure`、`simulateWriteFailure`、`simulateResizeFailure` 与 `simulateTerminateFailure`,精确验证业务层容错。
|
|
37
|
+
- 历史追踪与状态断言:维护 `spawnCalls`、`writes`、`eofCalls`、`interruptCalls`、`resizeCalls`、`terminateCalls` 与 `disposeCalls` 集合,供测试后置断言。
|
|
38
|
+
- 动态能力覆写:支持通过 `setCapabilities` 动态调整是否支持 PTY、resize 与 inputEOF 等特征。
|
|
39
|
+
|
|
31
40
|
### MockProcessExecutor 模拟进程执行器
|
|
32
41
|
|
|
33
|
-
[MockProcessExecutor](./src/process.ts)
|
|
42
|
+
[MockProcessExecutor](./src/process.ts) 深度集成 ProcessManager 与 FakeProcessDriver,拦截并模拟外部命令与受管进程调用:
|
|
34
43
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
44
|
+
- 规则灵活匹配:通过 `register` 注册匹配器,支持命令字符串精确匹配、正则表达式匹配或自定义断言谓词函数。
|
|
45
|
+
- 丰富响应定义:支持模拟标准输出、标准错误流、退出状态码、二进制字节流以及执行延迟。
|
|
46
|
+
- 受管进程穿透:直接暴露 `driver` 底层驱动与 `processManager` 实例,无缝承接 `run`、`start`、`acquire` 等受管操作。
|
|
47
|
+
- 调用历史追踪:精确记录每次调用的命令、参数、工作目录与环境变量。
|
|
39
48
|
|
|
40
49
|
### MemoryStorage 纯内存存储
|
|
41
50
|
|
|
@@ -49,36 +58,39 @@ ActionDock 2.0 确定性测试框架与测试运行时。
|
|
|
49
58
|
|
|
50
59
|
## 快速使用示例
|
|
51
60
|
|
|
61
|
+
### 一次性受管进程命令测试
|
|
62
|
+
|
|
52
63
|
```ts
|
|
53
64
|
import { describe, it } from "node:test";
|
|
54
65
|
import assert from "node:assert/strict";
|
|
55
|
-
import { defineAction } from "@actiondock/sdk";
|
|
56
|
-
import { createTestRuntime,
|
|
66
|
+
import { defineAction, decodeText } from "@actiondock/sdk";
|
|
67
|
+
import { createTestRuntime, FakeProcessDriver } from "@actiondock/testing";
|
|
57
68
|
|
|
58
69
|
const gitBranchAction = defineAction(async (input: { remote?: boolean }, ctx) => {
|
|
59
|
-
const res = await ctx.process.
|
|
60
|
-
|
|
70
|
+
const res = await ctx.process.run({
|
|
71
|
+
spec: { executable: "git", args: ["branch"], io: { mode: "pipe" } },
|
|
72
|
+
timeoutMs: 5000,
|
|
73
|
+
maxOutputBytes: 1024 * 1024,
|
|
74
|
+
});
|
|
75
|
+
return { output: decodeText(res.chunks).trim() };
|
|
61
76
|
});
|
|
62
77
|
|
|
63
78
|
describe("git action test", () => {
|
|
64
|
-
it("
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
it("使用 FakeProcessDriver 模拟输出并断言", async () => {
|
|
80
|
+
const fakeDriver = new FakeProcessDriver();
|
|
81
|
+
fakeDriver.onSpawn = (handle) => {
|
|
82
|
+
handle.emitOutput("stdout", "* main\n feature/agent\n");
|
|
83
|
+
handle.emitExit(0);
|
|
84
|
+
handle.emitOutputClosed("natural");
|
|
85
|
+
};
|
|
70
86
|
|
|
71
|
-
// 创建测试运行时并注入执行器
|
|
72
87
|
const runtime = createTestRuntime({
|
|
73
|
-
|
|
88
|
+
platform: { processDriver: fakeDriver } as any,
|
|
74
89
|
});
|
|
75
90
|
|
|
76
|
-
// 执行 Action 并断言业务数据
|
|
77
91
|
const result = await runtime.run(gitBranchAction, { remote: false });
|
|
78
92
|
assert.equal(result.output, "* main\n feature/agent");
|
|
79
|
-
|
|
80
|
-
// 断言底层命令调用历史
|
|
81
|
-
assert.equal(processExecutor.getHistory().length, 1);
|
|
93
|
+
assert.equal(fakeDriver.spawnCalls.length, 1);
|
|
82
94
|
});
|
|
83
95
|
});
|
|
84
96
|
```
|
package/dist/cli.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ExecCliOptions {
|
|
|
11
11
|
input?: string | Uint8Array;
|
|
12
12
|
encoding?: string;
|
|
13
13
|
throwOnError?: boolean;
|
|
14
|
+
/** 输出字节总量上限,超出后终止进程并标记 truncated(与 runtime-node 执行器语义对齐) */
|
|
15
|
+
maxOutputBytes?: number;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* CLI 执行结果结构体。
|
|
@@ -22,6 +24,8 @@ export interface ExecCliResult {
|
|
|
22
24
|
stderr: string;
|
|
23
25
|
raw: Uint8Array;
|
|
24
26
|
timedOut?: boolean;
|
|
27
|
+
/** 输出超过 maxOutputBytes 上限被截断时置为 true */
|
|
28
|
+
truncated?: boolean;
|
|
25
29
|
durationMs: number;
|
|
26
30
|
}
|
|
27
31
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ export { findExecutable };
|
|
|
12
12
|
*/
|
|
13
13
|
export async function execCli(command, args = [], options = {}) {
|
|
14
14
|
const startTime = performance.now();
|
|
15
|
+
const maxOutputBytes = options.maxOutputBytes ?? Infinity;
|
|
15
16
|
if (options.signal?.aborted) {
|
|
16
17
|
const errRes = {
|
|
17
18
|
ok: false,
|
|
@@ -78,9 +79,20 @@ export async function execCli(command, args = [], options = {}) {
|
|
|
78
79
|
}
|
|
79
80
|
let settled = false;
|
|
80
81
|
let timedOut = false;
|
|
82
|
+
let truncated = false;
|
|
81
83
|
let spawnError;
|
|
82
84
|
const stdoutChunks = [];
|
|
83
85
|
const stderrChunks = [];
|
|
86
|
+
let totalOutputBytes = 0;
|
|
87
|
+
// 输出超限时终止进程:由 enforceOutputLimit 负责截断与标记
|
|
88
|
+
const terminateForLimit = () => {
|
|
89
|
+
try {
|
|
90
|
+
child.kill("SIGTERM");
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// 忽略已退出状态
|
|
94
|
+
}
|
|
95
|
+
};
|
|
84
96
|
let timeoutTimer;
|
|
85
97
|
if (options.timeout && options.timeout > 0) {
|
|
86
98
|
timeoutTimer = setTimeout(() => {
|
|
@@ -112,7 +124,12 @@ export async function execCli(command, args = [], options = {}) {
|
|
|
112
124
|
stderr = `Command '${command}' timed out after ${options.timeout}ms`;
|
|
113
125
|
}
|
|
114
126
|
const exitCode = timedOut ? -1 : (exitCodeFromClose ?? (spawnError ? -1 : 0));
|
|
115
|
-
const ok = !timedOut && exitCode === 0;
|
|
127
|
+
const ok = !timedOut && !truncated && exitCode === 0;
|
|
128
|
+
let limitMessage = "";
|
|
129
|
+
if (truncated && !stderr) {
|
|
130
|
+
limitMessage = `Command '${command}' output exceeded limit of ${maxOutputBytes} bytes`;
|
|
131
|
+
stderr = limitMessage;
|
|
132
|
+
}
|
|
116
133
|
const result = {
|
|
117
134
|
ok,
|
|
118
135
|
exitCode,
|
|
@@ -120,6 +137,7 @@ export async function execCli(command, args = [], options = {}) {
|
|
|
120
137
|
stderr,
|
|
121
138
|
raw: rawStdout,
|
|
122
139
|
timedOut: timedOut || undefined,
|
|
140
|
+
truncated: truncated || undefined,
|
|
123
141
|
durationMs,
|
|
124
142
|
};
|
|
125
143
|
if (options.throwOnError && !ok) {
|
|
@@ -128,11 +146,35 @@ export async function execCli(command, args = [], options = {}) {
|
|
|
128
146
|
}
|
|
129
147
|
resolve(result);
|
|
130
148
|
};
|
|
149
|
+
// 输出超限时截断已收集字节并终止进程:与 runtime-node 执行器对齐,
|
|
150
|
+
// 只保留上限内的字节(含同 chunk 内截断),丢弃超限部分
|
|
151
|
+
const enforceOutputLimit = (chunks, chunk) => {
|
|
152
|
+
const remaining = maxOutputBytes - totalOutputBytes;
|
|
153
|
+
if (remaining < chunk.length) {
|
|
154
|
+
if (remaining > 0) {
|
|
155
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
156
|
+
totalOutputBytes += remaining;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
totalOutputBytes += chunk.length;
|
|
160
|
+
}
|
|
161
|
+
truncated = true;
|
|
162
|
+
terminateForLimit();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
totalOutputBytes += chunk.length;
|
|
166
|
+
chunks.push(chunk);
|
|
167
|
+
return false;
|
|
168
|
+
};
|
|
131
169
|
child.stdout?.on("data", (chunk) => {
|
|
132
|
-
|
|
170
|
+
if (truncated)
|
|
171
|
+
return;
|
|
172
|
+
enforceOutputLimit(stdoutChunks, chunk);
|
|
133
173
|
});
|
|
134
174
|
child.stderr?.on("data", (chunk) => {
|
|
135
|
-
|
|
175
|
+
if (truncated)
|
|
176
|
+
return;
|
|
177
|
+
enforceOutputLimit(stderrChunks, chunk);
|
|
136
178
|
});
|
|
137
179
|
child.on("error", (err) => {
|
|
138
180
|
spawnError = err;
|
package/dist/clock.d.ts
CHANGED
|
@@ -38,11 +38,25 @@ export declare class FakeClock implements Clock {
|
|
|
38
38
|
sleep(ms: number): Promise<void>;
|
|
39
39
|
/**
|
|
40
40
|
* 手动向前推进指定毫秒时间。
|
|
41
|
-
*
|
|
41
|
+
* 严格按时间戳递增顺序触发并完成所有到期的休眠计时器;
|
|
42
|
+
* 每轮先排空微任务再检查队列,确保多层 async 边界内链式注册的
|
|
43
|
+
* 已到期 sleep 在本次 advance 终点前全部触发(含链首有 await 边界的场景)。
|
|
42
44
|
*
|
|
43
45
|
* @param ms 推进的毫秒数
|
|
44
46
|
*/
|
|
45
47
|
advance(ms: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* 循环排空微任务队列直至稳定(一轮排空后无新的已到期 sleep 注册),
|
|
50
|
+
* 保证链式 sleep 在本次 advance 终点前全部触发。
|
|
51
|
+
*
|
|
52
|
+
* 仅排空微任务,不等待任何宏任务(定时器、IO):链式 sleep 依赖的
|
|
53
|
+
* async 回调链全部由微任务驱动,无需也无法跨越宏任务边界。
|
|
54
|
+
* 使用 setImmediate 兜底作为「微任务队列已排空」的观测哨兵:
|
|
55
|
+
* setImmediate 回调只能从事件循环检查点进入,它的执行必然意味着
|
|
56
|
+
* 在它之前排队的全部微任务(无论多少层 await 边界)都已完成,
|
|
57
|
+
* 以此获得不依赖拍数猜测的精确稳定性判定。
|
|
58
|
+
*/
|
|
59
|
+
private drainMicrotasks;
|
|
46
60
|
/**
|
|
47
61
|
* 获取当前等待中的计时器数量。
|
|
48
62
|
*/
|
package/dist/clock.js
CHANGED
|
@@ -59,7 +59,9 @@ export class FakeClock {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* 手动向前推进指定毫秒时间。
|
|
62
|
-
*
|
|
62
|
+
* 严格按时间戳递增顺序触发并完成所有到期的休眠计时器;
|
|
63
|
+
* 每轮先排空微任务再检查队列,确保多层 async 边界内链式注册的
|
|
64
|
+
* 已到期 sleep 在本次 advance 终点前全部触发(含链首有 await 边界的场景)。
|
|
63
65
|
*
|
|
64
66
|
* @param ms 推进的毫秒数
|
|
65
67
|
*/
|
|
@@ -67,27 +69,64 @@ export class FakeClock {
|
|
|
67
69
|
if (ms < 0) {
|
|
68
70
|
throw new Error("Cannot advance clock by negative time");
|
|
69
71
|
}
|
|
70
|
-
if (ms === 0) {
|
|
71
|
-
await Promise.resolve();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
72
|
const destinationMonotonic = this.currentMonotonic + ms;
|
|
75
73
|
const destinationNow = this.currentNow + ms;
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
if (ms > 0) {
|
|
75
|
+
this.advancedMs += ms;
|
|
76
|
+
}
|
|
77
|
+
// 先同步处理当前已到期项,再排空微任务后复查:resolve 触发的回调链
|
|
78
|
+
// 可能在任意深度的 await 边界后注册新的到期 sleep,每轮排空后重新检查。
|
|
79
|
+
// 首轮无到期项时全程不经过 await,保持「无等待计时器时同步推进时间」契约
|
|
80
|
+
for (;;) {
|
|
78
81
|
const nextSleep = this.pendingSleeps[0];
|
|
79
|
-
if (nextSleep.targetMonotonic > destinationMonotonic) {
|
|
82
|
+
if (!nextSleep || nextSleep.targetMonotonic > destinationMonotonic) {
|
|
80
83
|
break;
|
|
81
84
|
}
|
|
82
85
|
this.pendingSleeps.shift();
|
|
83
86
|
this.currentMonotonic = nextSleep.targetMonotonic;
|
|
84
87
|
this.currentNow = nextSleep.targetNow;
|
|
85
88
|
nextSleep.resolve();
|
|
86
|
-
await
|
|
89
|
+
await this.drainMicrotasks();
|
|
87
90
|
}
|
|
88
91
|
this.currentMonotonic = destinationMonotonic;
|
|
89
92
|
this.currentNow = destinationNow;
|
|
90
|
-
await
|
|
93
|
+
await this.drainMicrotasks();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 循环排空微任务队列直至稳定(一轮排空后无新的已到期 sleep 注册),
|
|
97
|
+
* 保证链式 sleep 在本次 advance 终点前全部触发。
|
|
98
|
+
*
|
|
99
|
+
* 仅排空微任务,不等待任何宏任务(定时器、IO):链式 sleep 依赖的
|
|
100
|
+
* async 回调链全部由微任务驱动,无需也无法跨越宏任务边界。
|
|
101
|
+
* 使用 setImmediate 兜底作为「微任务队列已排空」的观测哨兵:
|
|
102
|
+
* setImmediate 回调只能从事件循环检查点进入,它的执行必然意味着
|
|
103
|
+
* 在它之前排队的全部微任务(无论多少层 await 边界)都已完成,
|
|
104
|
+
* 以此获得不依赖拍数猜测的精确稳定性判定。
|
|
105
|
+
*/
|
|
106
|
+
drainMicrotasks() {
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
setImmediate(() => {
|
|
109
|
+
// setImmediate 触发时本轮微任务已全部排空;若排空期间注册了新的
|
|
110
|
+
// 已到期 sleep,再排一轮直至稳定。上限保护防御异常场景下的
|
|
111
|
+
// 自续注册(正常链式回调远达不到此深度)。
|
|
112
|
+
let rounds = 0;
|
|
113
|
+
const check = () => {
|
|
114
|
+
const pendingBefore = this.pendingSleeps.length;
|
|
115
|
+
setImmediate(() => {
|
|
116
|
+
if (this.pendingSleeps.length === pendingBefore) {
|
|
117
|
+
resolve();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (++rounds >= 10000) {
|
|
121
|
+
resolve();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
check();
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
check();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
91
130
|
}
|
|
92
131
|
/**
|
|
93
132
|
* 获取当前等待中的计时器数量。
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/platform.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type EventSink, type FileSystem, type ModuleLoader, type RuntimePlatform, type RuntimeStorage, type StorageFactory } from "@actiondock/core";
|
|
1
|
+
import { type EventSink, type FileSystem, type ModuleLoader, type ProcessManager, type RuntimePlatform, type RuntimeStorage, type StorageFactory } from "@actiondock/core";
|
|
2
2
|
import { FakeClock } from "./clock.js";
|
|
3
3
|
import { MockProcessExecutor } from "./process.js";
|
|
4
|
+
import { FakeProcessDriver } from "./process-driver.js";
|
|
4
5
|
/**
|
|
5
6
|
* 测试平台构建配置选项。
|
|
6
7
|
*/
|
|
@@ -13,6 +14,10 @@ export interface TestPlatformOptions {
|
|
|
13
14
|
globalStorage?: RuntimeStorage;
|
|
14
15
|
/** 可选注入的模拟进程执行器 */
|
|
15
16
|
process?: MockProcessExecutor;
|
|
17
|
+
/** 可选注入的底层进程驱动 */
|
|
18
|
+
processDriver?: FakeProcessDriver;
|
|
19
|
+
/** 可选注入的受管进程管理器 */
|
|
20
|
+
processManager?: ProcessManager;
|
|
16
21
|
/** 可选注入的执行事件接收器 */
|
|
17
22
|
eventSink?: EventSink;
|
|
18
23
|
/** 可选注入的文件系统抽象驱动 */
|
package/dist/platform.js
CHANGED
|
@@ -17,7 +17,11 @@ import { MemoryStorage } from "./storage.js";
|
|
|
17
17
|
*/
|
|
18
18
|
export function createTestPlatform(options = {}) {
|
|
19
19
|
const clock = options.clock ?? new FakeClock();
|
|
20
|
-
const process = options.process ??
|
|
20
|
+
const process = options.process ??
|
|
21
|
+
new MockProcessExecutor({
|
|
22
|
+
driver: options.processDriver,
|
|
23
|
+
processManager: options.processManager,
|
|
24
|
+
});
|
|
21
25
|
const eventSink = options.eventSink ?? new TestEventSink();
|
|
22
26
|
const files = options.files ?? new NodeFileSystem();
|
|
23
27
|
const modules = options.modules ?? new DefaultModuleLoader();
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { Capabilities, LaunchSpec } from "@actiondock/sdk";
|
|
2
|
+
import type { ProcessDriver, ProcessDriverCallbacks, ProcessDriverHandle, ProcessHandle, ProcessObserver } from "@actiondock/core";
|
|
3
|
+
export type { ProcessDriver, ProcessObserver, ProcessHandle };
|
|
4
|
+
/**
|
|
5
|
+
* 记录的标准写入操作条目。
|
|
6
|
+
*/
|
|
7
|
+
export interface RecordedWrite {
|
|
8
|
+
/** 目标进程句柄 */
|
|
9
|
+
handle: ProcessHandle;
|
|
10
|
+
/** 写入的二进制字节数据 */
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
/** 调用发生时间戳 */
|
|
13
|
+
timestamp: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 记录的标准输入 EOF 调用条目。
|
|
17
|
+
*/
|
|
18
|
+
export interface RecordedEOF {
|
|
19
|
+
/** 目标进程句柄 */
|
|
20
|
+
handle: ProcessHandle;
|
|
21
|
+
/** 调用发生时间戳 */
|
|
22
|
+
timestamp: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 记录的中断前台作业调用条目。
|
|
26
|
+
*/
|
|
27
|
+
export interface RecordedInterrupt {
|
|
28
|
+
/** 目标进程句柄 */
|
|
29
|
+
handle: ProcessHandle;
|
|
30
|
+
/** 调用发生时间戳 */
|
|
31
|
+
timestamp: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 记录的调整尺寸调用条目。
|
|
35
|
+
*/
|
|
36
|
+
export interface RecordedResize {
|
|
37
|
+
/** 目标进程句柄 */
|
|
38
|
+
handle: ProcessHandle;
|
|
39
|
+
/** 列数 */
|
|
40
|
+
cols: number;
|
|
41
|
+
/** 行数 */
|
|
42
|
+
rows: number;
|
|
43
|
+
/** 调用发生时间戳 */
|
|
44
|
+
timestamp: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 记录的终止进程调用条目。
|
|
48
|
+
*/
|
|
49
|
+
export interface RecordedTerminate {
|
|
50
|
+
/** 目标进程句柄 */
|
|
51
|
+
handle: ProcessHandle;
|
|
52
|
+
/** 宽限退出时限 */
|
|
53
|
+
graceMs: number;
|
|
54
|
+
/** 调用发生时间戳 */
|
|
55
|
+
timestamp: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 记录的销毁进程调用条目。
|
|
59
|
+
*/
|
|
60
|
+
export interface RecordedDispose {
|
|
61
|
+
/** 目标进程句柄 */
|
|
62
|
+
handle: ProcessHandle;
|
|
63
|
+
/** 调用发生时间戳 */
|
|
64
|
+
timestamp: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 记录的进程派生启动调用条目。
|
|
68
|
+
*/
|
|
69
|
+
export interface RecordedSpawn {
|
|
70
|
+
/** 生成的进程句柄 */
|
|
71
|
+
handle: ProcessHandle;
|
|
72
|
+
/** 进程启动规范 */
|
|
73
|
+
spec: LaunchSpec;
|
|
74
|
+
/** 进程观察者对象 */
|
|
75
|
+
observer: ProcessObserver;
|
|
76
|
+
/** 调用发生时间戳 */
|
|
77
|
+
timestamp: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 确定性测试专用进程驱动桩。
|
|
81
|
+
* 遵循《ActionDock Managed Process 设计 v2》第 11、12 节契约:
|
|
82
|
+
* - 支持确定性模拟输出(emitOutput)
|
|
83
|
+
* - 支持确定性模拟退出(emitExit)
|
|
84
|
+
* - 支持确定性模拟输出关闭(emitOutputClosed)
|
|
85
|
+
* - 支持确定性模拟故障注入(emitFault)
|
|
86
|
+
* - 记录完整写入(writes)、EOF 调用、resize 与 terminate 操作历史
|
|
87
|
+
* - 支持模拟各阶段操作失败注入用于健壮性测试
|
|
88
|
+
*/
|
|
89
|
+
export declare class FakeProcessDriver implements ProcessDriver {
|
|
90
|
+
private capabilities;
|
|
91
|
+
private readonly handles;
|
|
92
|
+
/** 记录所有派生调用 */
|
|
93
|
+
readonly spawnCalls: RecordedSpawn[];
|
|
94
|
+
/** 记录所有写入调用 */
|
|
95
|
+
readonly writes: RecordedWrite[];
|
|
96
|
+
/** 记录所有 EOF 调用 */
|
|
97
|
+
readonly eofCalls: RecordedEOF[];
|
|
98
|
+
/** 记录所有前台中断调用 */
|
|
99
|
+
readonly interruptCalls: RecordedInterrupt[];
|
|
100
|
+
/** 记录所有调整尺寸调用 */
|
|
101
|
+
readonly resizeCalls: RecordedResize[];
|
|
102
|
+
/** 记录所有终止调用 */
|
|
103
|
+
readonly terminateCalls: RecordedTerminate[];
|
|
104
|
+
/** 记录所有销毁调用 */
|
|
105
|
+
readonly disposeCalls: RecordedDispose[];
|
|
106
|
+
/** 故障注入:下一次派生将抛出的异常 */
|
|
107
|
+
nextSpawnError?: Error;
|
|
108
|
+
/** 故障注入:写入时将抛出的异常 */
|
|
109
|
+
nextWriteError?: Error;
|
|
110
|
+
/** 故障注入:调整尺寸时将抛出的异常 */
|
|
111
|
+
nextResizeError?: Error;
|
|
112
|
+
/** 故障注入:终止时将抛出的异常 */
|
|
113
|
+
nextTerminateError?: Error;
|
|
114
|
+
/** 自动响应回调:在进程派生后触发 */
|
|
115
|
+
onSpawn?: (handle: ProcessHandle, spec: LaunchSpec, observer: ProcessObserver) => void;
|
|
116
|
+
constructor(capabilities?: Partial<Capabilities>);
|
|
117
|
+
/**
|
|
118
|
+
* 获取驱动能力集合。
|
|
119
|
+
*/
|
|
120
|
+
getCapabilities(): Capabilities;
|
|
121
|
+
/**
|
|
122
|
+
* 覆盖驱动能力集合。
|
|
123
|
+
*/
|
|
124
|
+
setCapabilities(caps: Partial<Capabilities>): void;
|
|
125
|
+
/**
|
|
126
|
+
* 派生启动新进程,支持标准与旧版重载签名。
|
|
127
|
+
*/
|
|
128
|
+
spawn(spec: LaunchSpec, observer: ProcessObserver): Promise<ProcessHandle>;
|
|
129
|
+
spawn(processId: string, spec: LaunchSpec, callbacks: ProcessDriverCallbacks): Promise<ProcessDriverHandle>;
|
|
130
|
+
/**
|
|
131
|
+
* 标准接口实现派生新进程。
|
|
132
|
+
*/
|
|
133
|
+
private spawnStandard;
|
|
134
|
+
/**
|
|
135
|
+
* 模拟写入数据并记录历史。
|
|
136
|
+
*/
|
|
137
|
+
write(handle: ProcessHandle, data: Uint8Array): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* 模拟输入流关闭并记录历史。
|
|
140
|
+
*/
|
|
141
|
+
inputEOF(handle: ProcessHandle): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* 模拟中断前台作业并记录历史。
|
|
144
|
+
*/
|
|
145
|
+
interruptForeground(handle: ProcessHandle): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* 模拟调整终端尺寸并记录历史。
|
|
148
|
+
*/
|
|
149
|
+
resize(handle: ProcessHandle, cols: number, rows: number): Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* 模拟终止进程并记录历史。
|
|
152
|
+
*
|
|
153
|
+
* 终止语义与 MemoryProcessDriver 对齐:除非测试通过 nextTerminateError
|
|
154
|
+
* 注入故障,否则终止后进程必须退出(触发 exited 回调),否则
|
|
155
|
+
* ProcessManager.run 的超时/超限路径在 terminate 后永远收不到退出事件,
|
|
156
|
+
* 调用方会永久挂起。需要非退出语义的用例可先 setExitBehavior 或
|
|
157
|
+
* 直接使用 handle 上的确定性模拟接口自行控制退出时机。
|
|
158
|
+
*/
|
|
159
|
+
terminate(handle: ProcessHandle, graceMs: number): Promise<void>;
|
|
160
|
+
terminate(processId: string, graceMs: number): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* 模拟销毁进程并记录历史。
|
|
163
|
+
*/
|
|
164
|
+
dispose(handle: ProcessHandle): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* 确定性模拟向观察者发送输出。
|
|
167
|
+
*/
|
|
168
|
+
emitOutput(handleOrId: ProcessHandle | string, stream: "stdout" | "stderr" | "pty", data: Uint8Array | string): void;
|
|
169
|
+
/**
|
|
170
|
+
* 确定性模拟进程退出事件。
|
|
171
|
+
*/
|
|
172
|
+
emitExit(handleOrId: ProcessHandle | string, result?: {
|
|
173
|
+
code?: number | null;
|
|
174
|
+
signal?: string | null;
|
|
175
|
+
} | number): void;
|
|
176
|
+
/**
|
|
177
|
+
* 确定性模拟输出流彻底关闭事件。
|
|
178
|
+
*/
|
|
179
|
+
emitOutputClosed(handleOrId: ProcessHandle | string, reason?: "natural" | "drain-timeout" | "host-lost"): void;
|
|
180
|
+
/**
|
|
181
|
+
* 确定性模拟驱动故障通知。
|
|
182
|
+
*/
|
|
183
|
+
emitFault(handleOrId: ProcessHandle | string, error: Error): void;
|
|
184
|
+
/**
|
|
185
|
+
* 注入下一次 spawn 故障异常。
|
|
186
|
+
*/
|
|
187
|
+
simulateSpawnFailure(error: Error): void;
|
|
188
|
+
/**
|
|
189
|
+
* 注入下一次 write 故障异常。
|
|
190
|
+
*/
|
|
191
|
+
simulateWriteFailure(error: Error): void;
|
|
192
|
+
/**
|
|
193
|
+
* 注入下一次 resize 故障异常。
|
|
194
|
+
*/
|
|
195
|
+
simulateResizeFailure(error: Error): void;
|
|
196
|
+
/**
|
|
197
|
+
* 注入下一次 terminate 故障异常。
|
|
198
|
+
*/
|
|
199
|
+
simulateTerminateFailure(error: Error): void;
|
|
200
|
+
/**
|
|
201
|
+
* 获取最近创建的进程句柄。
|
|
202
|
+
*/
|
|
203
|
+
getLastHandle(): ProcessHandle | undefined;
|
|
204
|
+
/**
|
|
205
|
+
* 查询指定句柄标识对应的句柄实例。
|
|
206
|
+
*/
|
|
207
|
+
getHandle(handleOrId: ProcessHandle | string): ProcessHandle | undefined;
|
|
208
|
+
/**
|
|
209
|
+
* 查询指定句柄对应的观察者。
|
|
210
|
+
*/
|
|
211
|
+
getObserver(handleOrId: ProcessHandle | string): ProcessObserver | undefined;
|
|
212
|
+
/**
|
|
213
|
+
* 获取针对指定句柄(或全部句柄)的写入字节切片列表。
|
|
214
|
+
*/
|
|
215
|
+
getWrites(handleOrId?: ProcessHandle | string): Uint8Array[];
|
|
216
|
+
/**
|
|
217
|
+
* 获取针对指定句柄(或全部句柄)写入的 UTF-8 解码文本列表。
|
|
218
|
+
*/
|
|
219
|
+
getWrittenStrings(handleOrId?: ProcessHandle | string): string[];
|
|
220
|
+
/**
|
|
221
|
+
* 判断指定句柄是否接收到了 EOF 输入结束通知。
|
|
222
|
+
*/
|
|
223
|
+
hasInputEOF(handleOrId?: ProcessHandle | string): boolean;
|
|
224
|
+
/**
|
|
225
|
+
* 判断指定句柄是否接收到了前台作业中断通知。
|
|
226
|
+
*/
|
|
227
|
+
hasInterrupted(handleOrId?: ProcessHandle | string): boolean;
|
|
228
|
+
/**
|
|
229
|
+
* 清除历史调用记录。
|
|
230
|
+
*/
|
|
231
|
+
clearHistory(): void;
|
|
232
|
+
/**
|
|
233
|
+
* 重置全部句柄、模拟规则与调用历史。
|
|
234
|
+
*/
|
|
235
|
+
reset(): void;
|
|
236
|
+
/**
|
|
237
|
+
* 兼容旧版基于 processId 派生新进程。
|
|
238
|
+
*/
|
|
239
|
+
private spawnLegacy;
|
|
240
|
+
/**
|
|
241
|
+
* 内部解析指定句柄或标识。
|
|
242
|
+
*/
|
|
243
|
+
private resolveInternal;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 兼容原有命名导出。
|
|
247
|
+
*/
|
|
248
|
+
export { FakeProcessDriver as MockProcessDriver };
|