@actiondock/testing 2.0.11 → 2.2.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 +45 -47
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +151 -0
- package/dist/clock.d.ts +54 -0
- package/dist/clock.js +108 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/platform.d.ts +48 -0
- package/dist/platform.js +61 -0
- package/dist/process.d.ts +117 -0
- package/dist/process.js +280 -0
- package/dist/runtime.d.ts +208 -0
- package/dist/runtime.js +426 -0
- package/dist/storage.d.ts +20 -0
- package/dist/storage.js +16 -0
- package/package.json +13 -12
- package/src/clock.ts +0 -136
- package/src/index.ts +0 -4
- package/src/process.ts +0 -394
- package/src/runtime.ts +0 -380
- package/src/storage.ts +0 -37
package/src/process.ts
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
import type { ProcessExecutor } from "@actiondock/core";
|
|
2
|
-
import type {
|
|
3
|
-
DetachedProcessOptions,
|
|
4
|
-
DetachedProcessResult,
|
|
5
|
-
ProcessExecOptions,
|
|
6
|
-
ProcessResult,
|
|
7
|
-
RuntimeError,
|
|
8
|
-
} from "@actiondock/sdk";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 模拟命令匹配器。
|
|
12
|
-
*/
|
|
13
|
-
export type CommandMatcher =
|
|
14
|
-
| string
|
|
15
|
-
| RegExp
|
|
16
|
-
| ((command: string, args: string[], options: ProcessExecOptions) => boolean);
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 模拟进程执行结果选项。
|
|
20
|
-
*/
|
|
21
|
-
export interface MockProcessResultOptions {
|
|
22
|
-
/** 命令是否执行成功 */
|
|
23
|
-
ok?: boolean;
|
|
24
|
-
/** 退出状态码 */
|
|
25
|
-
exitCode?: number | null;
|
|
26
|
-
/** 终止信号名称 */
|
|
27
|
-
signal?: string;
|
|
28
|
-
/** 标准输出内容 */
|
|
29
|
-
stdout?: string;
|
|
30
|
-
/** 标准错误内容 */
|
|
31
|
-
stderr?: string;
|
|
32
|
-
/** 原始字节数组输出 */
|
|
33
|
-
raw?: Uint8Array;
|
|
34
|
-
/** 是否标记为超时 */
|
|
35
|
-
timedOut?: boolean;
|
|
36
|
-
/** 是否标记为已取消 */
|
|
37
|
-
cancelled?: boolean;
|
|
38
|
-
/** 执行耗时毫秒数 */
|
|
39
|
-
durationMs?: number;
|
|
40
|
-
/** 运行时结构化错误 */
|
|
41
|
-
error?: RuntimeError;
|
|
42
|
-
/** 模拟执行延迟毫秒数 */
|
|
43
|
-
delayMs?: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 模拟进程处理器函数。
|
|
48
|
-
*/
|
|
49
|
-
export type MockProcessHandler = (
|
|
50
|
-
command: string,
|
|
51
|
-
args: string[],
|
|
52
|
-
options: ProcessExecOptions
|
|
53
|
-
) =>
|
|
54
|
-
| MockProcessResultOptions
|
|
55
|
-
| ProcessResult
|
|
56
|
-
| Promise<MockProcessResultOptions | ProcessResult>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 已记录的命令调用历史条目。
|
|
60
|
-
*/
|
|
61
|
-
export interface ProcessCall {
|
|
62
|
-
/** 执行命令名称 */
|
|
63
|
-
command: string;
|
|
64
|
-
/** 执行参数列表 */
|
|
65
|
-
args: string[];
|
|
66
|
-
/** 执行选项配置 */
|
|
67
|
-
options: ProcessExecOptions;
|
|
68
|
-
/** 调用发生时的时间戳 */
|
|
69
|
-
timestamp: number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 已记录的后台守护进程调用历史条目。
|
|
74
|
-
*/
|
|
75
|
-
export interface DetachedProcessCall {
|
|
76
|
-
/** 启动参数选项 */
|
|
77
|
-
options: DetachedProcessOptions;
|
|
78
|
-
/** 调用发生时的时间戳 */
|
|
79
|
-
timestamp: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface RegisteredMock {
|
|
83
|
-
matcher: CommandMatcher;
|
|
84
|
-
handler: MockProcessHandler | MockProcessResultOptions;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 模拟进程执行器实现。
|
|
89
|
-
* 遵循 ProcessExecutor 接口契约,支持预设命令响应、跟踪调用历史并模拟超时与取消场景。
|
|
90
|
-
*/
|
|
91
|
-
export class MockProcessExecutor implements ProcessExecutor {
|
|
92
|
-
private mocks: RegisteredMock[] = [];
|
|
93
|
-
public calls: ProcessCall[] = [];
|
|
94
|
-
public detachedCalls: DetachedProcessCall[] = [];
|
|
95
|
-
public defaultPid = 10001;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 注册模拟命令匹配与返回结果。
|
|
99
|
-
*
|
|
100
|
-
* @param matcher 匹配器(命令字符串、正则表达式或判断函数)
|
|
101
|
-
* @param handlerOrResult 预设执行结果或动态处理函数
|
|
102
|
-
*/
|
|
103
|
-
register(
|
|
104
|
-
matcher: CommandMatcher,
|
|
105
|
-
handlerOrResult: MockProcessHandler | MockProcessResultOptions
|
|
106
|
-
): this {
|
|
107
|
-
this.mocks.push({ matcher, handler: handlerOrResult });
|
|
108
|
-
return this;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* 执行外部命令并返回模拟结果。
|
|
113
|
-
*
|
|
114
|
-
* @param command 执行命令
|
|
115
|
-
* @param args 参数列表
|
|
116
|
-
* @param options 执行选项
|
|
117
|
-
*/
|
|
118
|
-
async exec(
|
|
119
|
-
command: string,
|
|
120
|
-
args: string[] = [],
|
|
121
|
-
options: ProcessExecOptions = {}
|
|
122
|
-
): Promise<ProcessResult> {
|
|
123
|
-
const startTime = Date.now();
|
|
124
|
-
this.calls.push({
|
|
125
|
-
command,
|
|
126
|
-
args: [...args],
|
|
127
|
-
options: { ...options },
|
|
128
|
-
timestamp: startTime,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// 检查调用前是否已中断
|
|
132
|
-
if (options.signal?.aborted) {
|
|
133
|
-
const res: ProcessResult = {
|
|
134
|
-
ok: false,
|
|
135
|
-
exitCode: null,
|
|
136
|
-
signal: "SIGTERM",
|
|
137
|
-
stdout: "",
|
|
138
|
-
stderr: "Process was cancelled by AbortSignal",
|
|
139
|
-
raw: new Uint8Array(),
|
|
140
|
-
timedOut: false,
|
|
141
|
-
cancelled: true,
|
|
142
|
-
durationMs: 0,
|
|
143
|
-
error: {
|
|
144
|
-
code: "PROCESS_CANCELLED",
|
|
145
|
-
message: "Process was cancelled by AbortSignal",
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
if (options.throwOnError) {
|
|
149
|
-
throw new Error(res.stderr);
|
|
150
|
-
}
|
|
151
|
-
return res;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const matchedMock = this.findMock(command, args, options);
|
|
155
|
-
let resolved: MockProcessResultOptions | ProcessResult;
|
|
156
|
-
|
|
157
|
-
if (!matchedMock) {
|
|
158
|
-
resolved = {
|
|
159
|
-
ok: true,
|
|
160
|
-
exitCode: 0,
|
|
161
|
-
stdout: "",
|
|
162
|
-
stderr: "",
|
|
163
|
-
};
|
|
164
|
-
} else if (typeof matchedMock.handler === "function") {
|
|
165
|
-
resolved = await matchedMock.handler(command, args, options);
|
|
166
|
-
} else {
|
|
167
|
-
resolved = matchedMock.handler;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 模拟延时控制
|
|
171
|
-
const maybeMock = resolved as MockProcessResultOptions;
|
|
172
|
-
if (typeof maybeMock.delayMs === "number" && maybeMock.delayMs > 0) {
|
|
173
|
-
await this.waitDelay(maybeMock.delayMs, options);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 组装标准化结果
|
|
177
|
-
const timedOut = Boolean(resolved.timedOut);
|
|
178
|
-
const cancelled = Boolean(resolved.cancelled || options.signal?.aborted);
|
|
179
|
-
const stdout = resolved.stdout ?? "";
|
|
180
|
-
const stderr = resolved.stderr ?? (timedOut ? "Process timed out" : cancelled ? "Process cancelled" : "");
|
|
181
|
-
const raw = resolved.raw ?? new TextEncoder().encode(stdout);
|
|
182
|
-
const exitCode =
|
|
183
|
-
resolved.exitCode !== undefined
|
|
184
|
-
? resolved.exitCode
|
|
185
|
-
: timedOut || cancelled
|
|
186
|
-
? null
|
|
187
|
-
: resolved.ok === false
|
|
188
|
-
? 1
|
|
189
|
-
: 0;
|
|
190
|
-
const ok =
|
|
191
|
-
resolved.ok !== undefined
|
|
192
|
-
? resolved.ok
|
|
193
|
-
: exitCode === 0 && !timedOut && !cancelled && !resolved.error;
|
|
194
|
-
const durationMs = resolved.durationMs ?? Date.now() - startTime;
|
|
195
|
-
|
|
196
|
-
let error = resolved.error;
|
|
197
|
-
if (!error) {
|
|
198
|
-
if (timedOut) {
|
|
199
|
-
error = {
|
|
200
|
-
code: "PROCESS_TIMEOUT",
|
|
201
|
-
message: `Process exceeded timeout of ${options.timeoutMs ?? durationMs}ms`,
|
|
202
|
-
};
|
|
203
|
-
} else if (cancelled) {
|
|
204
|
-
error = {
|
|
205
|
-
code: "PROCESS_CANCELLED",
|
|
206
|
-
message: "Process was cancelled by AbortSignal",
|
|
207
|
-
};
|
|
208
|
-
} else if (!ok) {
|
|
209
|
-
error = {
|
|
210
|
-
code: "PROCESS_FAILED",
|
|
211
|
-
message: stderr || `Process exited with code ${exitCode}`,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const finalResult: ProcessResult = {
|
|
217
|
-
ok,
|
|
218
|
-
exitCode,
|
|
219
|
-
signal: resolved.signal,
|
|
220
|
-
stdout,
|
|
221
|
-
stderr,
|
|
222
|
-
raw,
|
|
223
|
-
timedOut,
|
|
224
|
-
cancelled,
|
|
225
|
-
durationMs,
|
|
226
|
-
error,
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
if (!ok && options.throwOnError) {
|
|
230
|
-
throw new Error(stderr || `Process exited with code ${exitCode}`);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return finalResult;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* 启动模拟脱离父进程的后台进程。
|
|
238
|
-
*
|
|
239
|
-
* @param options 守护进程启动选项
|
|
240
|
-
*/
|
|
241
|
-
async spawnDetached(
|
|
242
|
-
options: DetachedProcessOptions
|
|
243
|
-
): Promise<DetachedProcessResult> {
|
|
244
|
-
const startTime = Date.now();
|
|
245
|
-
this.detachedCalls.push({
|
|
246
|
-
options: { ...options },
|
|
247
|
-
timestamp: startTime,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
if (options.signal?.aborted) {
|
|
251
|
-
return {
|
|
252
|
-
ok: false,
|
|
253
|
-
ready: false,
|
|
254
|
-
durationMs: 0,
|
|
255
|
-
error: {
|
|
256
|
-
code: "PROCESS_CANCELLED",
|
|
257
|
-
message: "Process was cancelled by AbortSignal",
|
|
258
|
-
},
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (options.probe) {
|
|
263
|
-
const fakeResult: ProcessResult = {
|
|
264
|
-
ok: true,
|
|
265
|
-
exitCode: 0,
|
|
266
|
-
stdout: "ready",
|
|
267
|
-
stderr: "",
|
|
268
|
-
raw: new TextEncoder().encode("ready"),
|
|
269
|
-
timedOut: false,
|
|
270
|
-
cancelled: false,
|
|
271
|
-
durationMs: 0,
|
|
272
|
-
};
|
|
273
|
-
const isReady = await options.probe(fakeResult);
|
|
274
|
-
return {
|
|
275
|
-
ok: isReady,
|
|
276
|
-
pid: this.defaultPid++,
|
|
277
|
-
ready: isReady,
|
|
278
|
-
durationMs: Date.now() - startTime,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
ok: true,
|
|
284
|
-
pid: this.defaultPid++,
|
|
285
|
-
ready: true,
|
|
286
|
-
durationMs: Date.now() - startTime,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 获取指定命令的历史调用记录。
|
|
292
|
-
*
|
|
293
|
-
* @param command 可选命令筛选
|
|
294
|
-
*/
|
|
295
|
-
getCalls(command?: string): ProcessCall[] {
|
|
296
|
-
if (!command) {
|
|
297
|
-
return [...this.calls];
|
|
298
|
-
}
|
|
299
|
-
return this.calls.filter((c) => c.command === command);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* 获取最近一次命令调用记录。
|
|
304
|
-
*/
|
|
305
|
-
getLastCall(): ProcessCall | undefined {
|
|
306
|
-
return this.calls[this.calls.length - 1];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* 检查指定命令是否被调用过。
|
|
311
|
-
*
|
|
312
|
-
* @param command 目标命令
|
|
313
|
-
*/
|
|
314
|
-
hasCalled(command: string): boolean {
|
|
315
|
-
return this.calls.some((c) => c.command === command);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* 清空历史调用记录。
|
|
320
|
-
*/
|
|
321
|
-
clearHistory(): void {
|
|
322
|
-
this.calls = [];
|
|
323
|
-
this.detachedCalls = [];
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* 重置所有注册规则与历史记录。
|
|
328
|
-
*/
|
|
329
|
-
reset(): void {
|
|
330
|
-
this.mocks = [];
|
|
331
|
-
this.calls = [];
|
|
332
|
-
this.detachedCalls = [];
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private findMock(
|
|
336
|
-
command: string,
|
|
337
|
-
args: string[],
|
|
338
|
-
options: ProcessExecOptions
|
|
339
|
-
): RegisteredMock | undefined {
|
|
340
|
-
const fullCommandLine = [command, ...args].join(" ").trim();
|
|
341
|
-
|
|
342
|
-
// 逆序查找,优先匹配最新注册的规则
|
|
343
|
-
for (let i = this.mocks.length - 1; i >= 0; i--) {
|
|
344
|
-
const mock = this.mocks[i];
|
|
345
|
-
if (typeof mock.matcher === "string") {
|
|
346
|
-
if (
|
|
347
|
-
mock.matcher === command ||
|
|
348
|
-
mock.matcher === fullCommandLine ||
|
|
349
|
-
fullCommandLine.startsWith(mock.matcher)
|
|
350
|
-
) {
|
|
351
|
-
return mock;
|
|
352
|
-
}
|
|
353
|
-
} else if (mock.matcher instanceof RegExp) {
|
|
354
|
-
if (mock.matcher.test(fullCommandLine) || mock.matcher.test(command)) {
|
|
355
|
-
return mock;
|
|
356
|
-
}
|
|
357
|
-
} else if (typeof mock.matcher === "function") {
|
|
358
|
-
if (mock.matcher(command, args, options)) {
|
|
359
|
-
return mock;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return undefined;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
private async waitDelay(
|
|
367
|
-
delayMs: number,
|
|
368
|
-
options: ProcessExecOptions
|
|
369
|
-
): Promise<void> {
|
|
370
|
-
return new Promise<void>((resolve) => {
|
|
371
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
372
|
-
|
|
373
|
-
const cleanup = () => {
|
|
374
|
-
if (timer) clearTimeout(timer);
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
if (options.signal) {
|
|
378
|
-
options.signal.addEventListener(
|
|
379
|
-
"abort",
|
|
380
|
-
() => {
|
|
381
|
-
cleanup();
|
|
382
|
-
resolve();
|
|
383
|
-
},
|
|
384
|
-
{ once: true }
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
timer = setTimeout(() => {
|
|
389
|
-
cleanup();
|
|
390
|
-
resolve();
|
|
391
|
-
}, delayMs);
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|