self-bench 0.3.0 → 0.3.3
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/.dockerignore +9 -0
- package/Dockerfile +38 -0
- package/Dockerfile.sandbox +24 -0
- package/README.md +103 -171
- package/biome.json +18 -0
- package/bun.lock +1182 -0
- package/compose.yaml +85 -0
- package/dist/agent-smoke-main.js +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +40 -2
- package/dist/api.js.map +1 -1
- package/dist/artifacts.d.ts +0 -1
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +0 -3
- package/dist/artifacts.js.map +1 -1
- package/dist/build-metadata.d.ts +2 -0
- package/dist/build-metadata.d.ts.map +1 -0
- package/dist/build-metadata.js +2 -0
- package/dist/build-metadata.js.map +1 -0
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/codex-review.d.ts +2 -1
- package/dist/codex-review.d.ts.map +1 -1
- package/dist/codex-review.js +26 -22
- package/dist/codex-review.js.map +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -2
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/eval-main.js +3 -3
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +12 -7
- package/dist/evaluate.js.map +1 -1
- package/dist/provenance.d.ts +0 -1
- package/dist/provenance.d.ts.map +1 -1
- package/dist/provenance.js.map +1 -1
- package/dist/reaudit-main.js +10 -7
- package/dist/reaudit-main.js.map +1 -1
- package/dist/repair-main.js +8 -5
- package/dist/repair-main.js.map +1 -1
- package/dist/review/assets/index-CZ3G759O.js +1568 -0
- package/dist/review/assets/index-DgSLzX8l.css +1 -0
- package/dist/review/assets/{mojo-Blby8dQK.js → mojo-DJz3ZmWd.js} +1 -1
- package/dist/review/assets/typst-BUadGCkm.js +1 -0
- package/dist/review/index.html +2 -2
- package/dist/sandbox-repair.bundle.js +8 -7
- package/dist/sandbox-repair.js +7 -7
- package/dist/sandbox-repair.js.map +1 -1
- package/dist/sandbox-review.bundle.js +29 -25
- package/dist/sandbox-review.js +5 -3
- package/dist/sandbox-review.js.map +1 -1
- package/dist/sandbox-validation-repair.bundle.js +9 -9
- package/dist/sandbox-validation-repair.js +11 -9
- package/dist/sandbox-validation-repair.js.map +1 -1
- package/dist/subscription-auth.d.ts +12 -1
- package/dist/subscription-auth.d.ts.map +1 -1
- package/dist/subscription-auth.js +16 -16
- package/dist/subscription-auth.js.map +1 -1
- package/dist/{activities.d.ts → temporal/activities.d.ts} +2 -3
- package/dist/temporal/activities.d.ts.map +1 -0
- package/dist/{activities.js → temporal/activities.js} +44 -33
- package/dist/temporal/activities.js.map +1 -0
- package/dist/{temporal.d.ts → temporal/connection.d.ts} +2 -2
- package/dist/temporal/connection.d.ts.map +1 -0
- package/dist/{temporal.js → temporal/connection.js} +1 -1
- package/dist/temporal/connection.js.map +1 -0
- package/dist/{worker-main.d.ts.map → temporal/worker-main.d.ts.map} +1 -1
- package/dist/{worker-main.js → temporal/worker-main.js} +5 -9
- package/dist/temporal/worker-main.js.map +1 -0
- package/dist/{workflow.d.ts → temporal/workflow.d.ts} +1 -1
- package/dist/temporal/workflow.d.ts.map +1 -0
- package/dist/temporal/workflow.js.map +1 -0
- package/dist/validate-main.js +1 -1
- package/docs/evaluations.md +68 -0
- package/docs/operations.md +169 -0
- package/docs/task-construction.md +94 -0
- package/package.json +33 -20
- package/scripts/verify-package.ts +57 -0
- package/scripts/write-build-metadata.ts +27 -0
- package/src/agent-smoke-main.ts +63 -0
- package/src/agent-smoke.ts +132 -0
- package/src/api-main.ts +12 -0
- package/src/api.ts +277 -0
- package/src/artifacts.ts +357 -0
- package/src/audit.ts +106 -0
- package/src/build-metadata.ts +3 -0
- package/src/cli.ts +359 -0
- package/src/codex-review.ts +229 -0
- package/src/config.ts +114 -0
- package/src/contracts.ts +204 -0
- package/src/coupling.ts +259 -0
- package/src/docker-executor.ts +115 -0
- package/src/eval-main.ts +92 -0
- package/src/evaluate.ts +298 -0
- package/src/github.ts +26 -0
- package/src/harbor-results.ts +142 -0
- package/src/harbor-task.ts +528 -0
- package/src/hash.ts +5 -0
- package/src/modal-auth.ts +11 -0
- package/src/modal-executor.ts +176 -0
- package/src/parallel.ts +24 -0
- package/src/process.ts +165 -0
- package/src/provenance.ts +457 -0
- package/src/reaudit-main.ts +195 -0
- package/src/repair-main.ts +206 -0
- package/src/repair.ts +55 -0
- package/src/run-wait.ts +40 -0
- package/src/sandbox-author.ts +19 -0
- package/src/sandbox-repair.ts +156 -0
- package/src/sandbox-review.ts +19 -0
- package/src/sandbox-validation-repair.ts +177 -0
- package/src/sandbox.ts +51 -0
- package/src/subscription-auth.ts +80 -0
- package/src/temporal/activities.ts +1241 -0
- package/src/temporal/connection.ts +23 -0
- package/src/temporal/worker-main.ts +28 -0
- package/src/temporal/workflow.ts +519 -0
- package/src/validate-main.ts +171 -0
- package/src/validation-repair.ts +94 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +21 -0
- package/dist/activities.d.ts.map +0 -1
- package/dist/activities.js.map +0 -1
- package/dist/extensions/review.d.ts +0 -3
- package/dist/extensions/review.d.ts.map +0 -1
- package/dist/extensions/review.js +0 -44
- package/dist/extensions/review.js.map +0 -1
- package/dist/review/assets/index-Dq-6DFzI.css +0 -1
- package/dist/review/assets/index-DurW_RD7.js +0 -1567
- package/dist/review/assets/typst-DHCkPAjA.js +0 -1
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js.map +0 -1
- package/dist/worker-main.js.map +0 -1
- package/dist/workflow.d.ts.map +0 -1
- package/dist/workflow.js.map +0 -1
- package/src/extensions/review.ts +0 -54
- /package/dist/{worker-main.d.ts → temporal/worker-main.d.ts} +0 -0
- /package/dist/{workflow.js → temporal/workflow.js} +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { type Image, ModalClient, type Secret } from "modal";
|
|
2
|
+
import type { SelfBenchConfig } from "./config.js";
|
|
3
|
+
import { InactivityTimeoutError, RollingOutput } from "./process.js";
|
|
4
|
+
import type {
|
|
5
|
+
SandboxExecutor,
|
|
6
|
+
SandboxRequest,
|
|
7
|
+
SandboxResult,
|
|
8
|
+
SandboxRunOptions,
|
|
9
|
+
} from "./sandbox.js";
|
|
10
|
+
|
|
11
|
+
export class ModalSandboxExecutor implements SandboxExecutor {
|
|
12
|
+
readonly #client: ModalClient;
|
|
13
|
+
readonly #config: Extract<SelfBenchConfig["execution"], { kind: "modal" }>;
|
|
14
|
+
#image: Image | undefined;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
config: Extract<SelfBenchConfig["execution"], { kind: "modal" }>,
|
|
18
|
+
client = new ModalClient(),
|
|
19
|
+
) {
|
|
20
|
+
this.#config = config;
|
|
21
|
+
this.#client = client;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async run(request: SandboxRequest, options: SandboxRunOptions = {}): Promise<SandboxResult> {
|
|
25
|
+
const app = await this.#client.apps.fromName(this.#config.app, {
|
|
26
|
+
createIfMissing: true,
|
|
27
|
+
...(this.#config.environment ? { environment: this.#config.environment } : {}),
|
|
28
|
+
});
|
|
29
|
+
if (!this.#image) {
|
|
30
|
+
this.#image = this.#buildImage();
|
|
31
|
+
}
|
|
32
|
+
const sandbox = await this.#client.sandboxes.create(app, this.#image, {
|
|
33
|
+
timeoutMs: request.timeoutMs,
|
|
34
|
+
idleTimeoutMs: Math.min(request.timeoutMs, 10 * 60 * 1000),
|
|
35
|
+
cpu: request.cpu ?? 4,
|
|
36
|
+
memoryMiB: request.memoryMiB ?? 8192,
|
|
37
|
+
workdir: "/work",
|
|
38
|
+
name: sandboxName(request.runId, request.stage),
|
|
39
|
+
tags: { run_id: request.runId, stage: request.stage },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const abort = () => {
|
|
43
|
+
void sandbox.terminate();
|
|
44
|
+
};
|
|
45
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
46
|
+
try {
|
|
47
|
+
for (const file of request.files ?? []) {
|
|
48
|
+
if (typeof file.contents === "string") {
|
|
49
|
+
await sandbox.filesystem.writeText(file.contents, file.path);
|
|
50
|
+
} else {
|
|
51
|
+
await sandbox.filesystem.writeBytes(file.contents, file.path);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const secrets = await this.#secrets(request.secrets);
|
|
56
|
+
const process = await sandbox.exec([...request.command], {
|
|
57
|
+
env: { ...request.environment },
|
|
58
|
+
...(secrets.length > 0 ? { secrets } : {}),
|
|
59
|
+
});
|
|
60
|
+
await process.closeStdin();
|
|
61
|
+
const stdoutOutput = new RollingOutput();
|
|
62
|
+
const stderrOutput = new RollingOutput();
|
|
63
|
+
let inactivityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
+
let inactivityError: InactivityTimeoutError | undefined;
|
|
65
|
+
const clearInactivityTimer = (): void => {
|
|
66
|
+
if (inactivityTimer) {
|
|
67
|
+
clearTimeout(inactivityTimer);
|
|
68
|
+
inactivityTimer = undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const armInactivityTimer = (): void => {
|
|
72
|
+
clearInactivityTimer();
|
|
73
|
+
if (!request.inactivityTimeoutMs) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
inactivityTimer = setTimeout(() => {
|
|
77
|
+
inactivityError = new InactivityTimeoutError(
|
|
78
|
+
`Modal sandbox ${sandbox.sandboxId} stage ${request.stage}`,
|
|
79
|
+
request.inactivityTimeoutMs ?? 0,
|
|
80
|
+
);
|
|
81
|
+
void sandbox.terminate();
|
|
82
|
+
}, request.inactivityTimeoutMs);
|
|
83
|
+
inactivityTimer.unref();
|
|
84
|
+
};
|
|
85
|
+
const consume = async (
|
|
86
|
+
streamName: "stdout" | "stderr",
|
|
87
|
+
stream: ReadableStream<string | Uint8Array>,
|
|
88
|
+
output: RollingOutput,
|
|
89
|
+
): Promise<void> => {
|
|
90
|
+
const reader = stream.getReader();
|
|
91
|
+
try {
|
|
92
|
+
while (true) {
|
|
93
|
+
const { value, done } = await reader.read();
|
|
94
|
+
if (done) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (value !== undefined) {
|
|
98
|
+
const chunk = typeof value === "string" ? Buffer.from(value) : Buffer.from(value);
|
|
99
|
+
output.push(chunk);
|
|
100
|
+
options.onProgress?.({ stream: streamName, bytes: chunk.byteLength });
|
|
101
|
+
armInactivityTimer();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} finally {
|
|
105
|
+
reader.releaseLock();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
armInactivityTimer();
|
|
109
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
110
|
+
process.wait(),
|
|
111
|
+
consume("stdout", process.stdout, stdoutOutput),
|
|
112
|
+
consume("stderr", process.stderr, stderrOutput),
|
|
113
|
+
])
|
|
114
|
+
.then(([exitCode]) => {
|
|
115
|
+
if (inactivityError) {
|
|
116
|
+
throw inactivityError;
|
|
117
|
+
}
|
|
118
|
+
return [exitCode, stdoutOutput.text(), stderrOutput.text()] as const;
|
|
119
|
+
})
|
|
120
|
+
.catch((error: unknown) => {
|
|
121
|
+
throw inactivityError ?? error;
|
|
122
|
+
})
|
|
123
|
+
.finally(clearInactivityTimer);
|
|
124
|
+
const outputs: Record<string, Uint8Array> = {};
|
|
125
|
+
for (const path of request.outputPaths ?? []) {
|
|
126
|
+
try {
|
|
127
|
+
outputs[path] = await sandbox.filesystem.readBytes(path);
|
|
128
|
+
} catch {
|
|
129
|
+
// Callers validate required outputs after persisting stdout/stderr. Returning an
|
|
130
|
+
// absent output keeps the model or command failure diagnosable.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { sandboxId: sandbox.sandboxId, exitCode, stdout, stderr, outputs };
|
|
134
|
+
} finally {
|
|
135
|
+
options.signal?.removeEventListener("abort", abort);
|
|
136
|
+
await sandbox.terminate().catch(() => undefined);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
close(): void {
|
|
141
|
+
this.#client.close();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#buildImage(): Image {
|
|
145
|
+
return this.#client.images
|
|
146
|
+
.fromRegistry(this.#config.image)
|
|
147
|
+
.dockerfileCommands([
|
|
148
|
+
"USER root",
|
|
149
|
+
"RUN apt-get update && apt-get install -y --no-install-recommends bash ca-certificates curl git jq ripgrep unzip xz-utils && rm -rf /var/lib/apt/lists/*",
|
|
150
|
+
"RUN curl -fsSL https://github.com/cli/cli/releases/download/v2.89.0/gh_2.89.0_linux_amd64.tar.gz -o /tmp/gh.tar.gz && echo 'd0422caade520530e76c1c558da47daebaa8e1203d6b7ff10ad7d6faba3490d8 /tmp/gh.tar.gz' | sha256sum -c - && tar -xzf /tmp/gh.tar.gz -C /tmp && mv /tmp/gh_2.89.0_linux_amd64/bin/gh /usr/local/bin/gh && rm -rf /tmp/gh.tar.gz /tmp/gh_2.89.0_linux_amd64",
|
|
151
|
+
"RUN npm install --global --ignore-scripts @earendil-works/pi-coding-agent@0.84.0",
|
|
152
|
+
"RUN npm install --global @openai/codex@0.146.1",
|
|
153
|
+
"ENV PATH=/root/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin",
|
|
154
|
+
"WORKDIR /work",
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async #secrets(entries: Readonly<Record<string, string>> | undefined): Promise<Secret[]> {
|
|
159
|
+
if (!entries || Object.keys(entries).length === 0) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
return [
|
|
163
|
+
await this.#client.secrets.fromObject(
|
|
164
|
+
{ ...entries },
|
|
165
|
+
{
|
|
166
|
+
...(this.#config.environment ? { environment: this.#config.environment } : {}),
|
|
167
|
+
},
|
|
168
|
+
),
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function sandboxName(runId: string, stage: string): string {
|
|
174
|
+
const suffix = crypto.randomUUID().slice(0, 8);
|
|
175
|
+
return `${runId.slice(0, 24)}-${stage.slice(0, 16)}-${suffix}`.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
176
|
+
}
|
package/src/parallel.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function parallelMap<T, R>(
|
|
2
|
+
values: readonly T[],
|
|
3
|
+
concurrency: number,
|
|
4
|
+
action: (value: T) => Promise<R>,
|
|
5
|
+
): Promise<R[]> {
|
|
6
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
7
|
+
throw new Error("concurrency must be a positive integer");
|
|
8
|
+
}
|
|
9
|
+
const output = new Array<R>(values.length);
|
|
10
|
+
let cursor = 0;
|
|
11
|
+
await Promise.all(
|
|
12
|
+
Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
13
|
+
while (cursor < values.length) {
|
|
14
|
+
const index = cursor;
|
|
15
|
+
cursor += 1;
|
|
16
|
+
const value = values[index];
|
|
17
|
+
if (value !== undefined) {
|
|
18
|
+
output[index] = await action(value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
return output;
|
|
24
|
+
}
|
package/src/process.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export interface CommandResult {
|
|
4
|
+
readonly stdout: string;
|
|
5
|
+
readonly stderr: string;
|
|
6
|
+
readonly exitCode: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CommandOptions {
|
|
10
|
+
readonly cwd?: string;
|
|
11
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
12
|
+
readonly input?: Uint8Array | string;
|
|
13
|
+
readonly timeoutMs?: number;
|
|
14
|
+
readonly inactivityTimeoutMs?: number;
|
|
15
|
+
readonly allowFailure?: boolean;
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
readonly onOutput?: (stream: "stdout" | "stderr", chunk: Uint8Array) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class InactivityTimeoutError extends Error {
|
|
21
|
+
constructor(
|
|
22
|
+
readonly scope: string,
|
|
23
|
+
readonly timeoutMs: number,
|
|
24
|
+
) {
|
|
25
|
+
super(`${scope} produced no output for ${timeoutMs}ms`);
|
|
26
|
+
this.name = "InactivityTimeoutError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runCommand(
|
|
31
|
+
command: string,
|
|
32
|
+
args: readonly string[],
|
|
33
|
+
options: CommandOptions = {},
|
|
34
|
+
): Promise<CommandResult> {
|
|
35
|
+
return await new Promise((resolve, reject) => {
|
|
36
|
+
const child = spawn(command, args, {
|
|
37
|
+
cwd: options.cwd,
|
|
38
|
+
env: options.env ?? process.env,
|
|
39
|
+
stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
40
|
+
});
|
|
41
|
+
const stdout = new RollingOutput();
|
|
42
|
+
const stderr = new RollingOutput();
|
|
43
|
+
if (!child.stdout || !child.stderr) {
|
|
44
|
+
reject(new Error(`failed to capture ${command} output`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let inactivityTimer: NodeJS.Timeout | undefined;
|
|
48
|
+
let forceKillTimer: NodeJS.Timeout | undefined;
|
|
49
|
+
let inactivityError: InactivityTimeoutError | undefined;
|
|
50
|
+
const clearInactivityTimer = (): void => {
|
|
51
|
+
if (inactivityTimer) {
|
|
52
|
+
clearTimeout(inactivityTimer);
|
|
53
|
+
inactivityTimer = undefined;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const terminate = (): void => {
|
|
57
|
+
child.kill("SIGTERM");
|
|
58
|
+
forceKillTimer ??= setTimeout(() => child.kill("SIGKILL"), 5_000);
|
|
59
|
+
forceKillTimer.unref();
|
|
60
|
+
};
|
|
61
|
+
const armInactivityTimer = (): void => {
|
|
62
|
+
clearInactivityTimer();
|
|
63
|
+
if (!options.inactivityTimeoutMs) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
inactivityTimer = setTimeout(() => {
|
|
67
|
+
inactivityError = new InactivityTimeoutError(
|
|
68
|
+
`${command} ${args.join(" ")}`,
|
|
69
|
+
options.inactivityTimeoutMs ?? 0,
|
|
70
|
+
);
|
|
71
|
+
terminate();
|
|
72
|
+
}, options.inactivityTimeoutMs);
|
|
73
|
+
inactivityTimer.unref();
|
|
74
|
+
};
|
|
75
|
+
const capture = (stream: "stdout" | "stderr", output: RollingOutput, chunk: Buffer): void => {
|
|
76
|
+
output.push(chunk);
|
|
77
|
+
options.onOutput?.(stream, chunk);
|
|
78
|
+
armInactivityTimer();
|
|
79
|
+
};
|
|
80
|
+
child.stdout.on("data", (chunk: Buffer) => capture("stdout", stdout, chunk));
|
|
81
|
+
child.stderr.on("data", (chunk: Buffer) => capture("stderr", stderr, chunk));
|
|
82
|
+
child.on("error", reject);
|
|
83
|
+
|
|
84
|
+
const abort = () => terminate();
|
|
85
|
+
if (options.signal?.aborted) {
|
|
86
|
+
abort();
|
|
87
|
+
} else {
|
|
88
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const timeout = options.timeoutMs
|
|
92
|
+
? setTimeout(() => terminate(), options.timeoutMs)
|
|
93
|
+
: undefined;
|
|
94
|
+
timeout?.unref();
|
|
95
|
+
armInactivityTimer();
|
|
96
|
+
|
|
97
|
+
child.on("close", (exitCode, signal) => {
|
|
98
|
+
options.signal?.removeEventListener("abort", abort);
|
|
99
|
+
clearInactivityTimer();
|
|
100
|
+
if (forceKillTimer) {
|
|
101
|
+
clearTimeout(forceKillTimer);
|
|
102
|
+
}
|
|
103
|
+
if (timeout) {
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
}
|
|
106
|
+
const result = {
|
|
107
|
+
stdout: stdout.text(),
|
|
108
|
+
stderr: stderr.text(),
|
|
109
|
+
exitCode: exitCode ?? (signal ? 128 : 1),
|
|
110
|
+
};
|
|
111
|
+
if (inactivityError) {
|
|
112
|
+
reject(inactivityError);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (result.exitCode !== 0 && !options.allowFailure) {
|
|
116
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
117
|
+
reject(new Error(`${command} exited ${result.exitCode}${detail ? `: ${detail}` : ""}`));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
resolve(result);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (child.stdin && options.input !== undefined) {
|
|
124
|
+
child.stdin.end(options.input);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class RollingOutput {
|
|
130
|
+
readonly #limit = 8 * 1024 * 1024;
|
|
131
|
+
readonly #chunks: Buffer[] = [];
|
|
132
|
+
#size = 0;
|
|
133
|
+
#truncated = false;
|
|
134
|
+
|
|
135
|
+
push(chunk: Buffer): void {
|
|
136
|
+
if (chunk.byteLength >= this.#limit) {
|
|
137
|
+
this.#chunks.length = 0;
|
|
138
|
+
this.#chunks.push(chunk.subarray(chunk.byteLength - this.#limit));
|
|
139
|
+
this.#size = this.#limit;
|
|
140
|
+
this.#truncated = true;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.#chunks.push(chunk);
|
|
144
|
+
this.#size += chunk.byteLength;
|
|
145
|
+
while (this.#size > this.#limit) {
|
|
146
|
+
const first = this.#chunks.shift();
|
|
147
|
+
if (!first) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
const overflow = this.#size - this.#limit;
|
|
151
|
+
if (first.byteLength > overflow) {
|
|
152
|
+
this.#chunks.unshift(first.subarray(overflow));
|
|
153
|
+
this.#size -= overflow;
|
|
154
|
+
} else {
|
|
155
|
+
this.#size -= first.byteLength;
|
|
156
|
+
}
|
|
157
|
+
this.#truncated = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
text(): string {
|
|
162
|
+
const value = Buffer.concat(this.#chunks, this.#size).toString("utf8");
|
|
163
|
+
return this.#truncated ? `[selfbench: earlier output truncated]\n${value}` : value;
|
|
164
|
+
}
|
|
165
|
+
}
|