as-test 1.0.0 → 1.0.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.
@@ -1,38 +1,122 @@
1
1
  import { existsSync } from "fs";
2
2
  import { glob } from "glob";
3
3
  import chalk from "chalk";
4
- import { spawnSync } from "child_process";
4
+ import { spawn } from "child_process";
5
5
  import * as path from "path";
6
+ import { createMemoryStream, main as ascMain, } from "assemblyscript/dist/asc.js";
6
7
  import { applyMode, getPkgRunner, loadConfig, tokenizeCommand, resolveProjectModule, } from "../util.js";
8
+ import { persistCrashRecord } from "../crash-store.js";
9
+ import { BuildWorkerPool } from "../build-worker-pool.js";
7
10
  const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
8
- export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}) {
11
+ class BuildFailureError extends Error {
12
+ constructor(args) {
13
+ super(args.message);
14
+ this.name = "BuildFailureError";
15
+ this.file = args.file;
16
+ this.mode = args.mode;
17
+ this.invocation = args.invocation;
18
+ this.stdout = args.stdout;
19
+ this.stderr = args.stderr;
20
+ this.kind = args.kind;
21
+ }
22
+ }
23
+ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}, overrides = {}) {
9
24
  const loadedConfig = loadConfig(configPath, false);
10
25
  const mode = applyMode(loadedConfig, modeName);
11
- const config = mode.config;
26
+ const config = Object.assign(Object.create(Object.getPrototypeOf(mode.config)), mode.config);
27
+ config.buildOptions = Object.assign(Object.create(Object.getPrototypeOf(mode.config.buildOptions)), mode.config.buildOptions);
28
+ if (overrides.target) {
29
+ config.buildOptions.target = overrides.target;
30
+ }
31
+ if (overrides.args?.length) {
32
+ config.buildOptions.args = [...config.buildOptions.args, ...overrides.args];
33
+ }
12
34
  if (!hasCustomBuildCommand(config)) {
13
35
  ensureDeps(config);
14
36
  }
15
37
  const pkgRunner = getPkgRunner();
16
38
  const inputPatterns = resolveInputPatterns(config.input, selectors);
17
39
  const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
18
- const duplicateSpecBasenames = await resolveDuplicateSpecBasenames(config.input);
40
+ const duplicateSpecBasenames = resolveDuplicateBasenames(inputFiles);
19
41
  const coverageEnabled = resolveCoverageEnabled(config.coverage, featureToggles.coverage);
20
42
  const buildEnv = {
21
43
  ...mode.env,
44
+ ...config.buildOptions.env,
22
45
  AS_TEST_COVERAGE_ENABLED: coverageEnabled ? "1" : "0",
23
46
  };
47
+ if (!process.env.AS_TEST_BUILD_API && !hasCustomBuildCommand(config)) {
48
+ const pool = getSerialBuildWorkerPool();
49
+ for (const file of inputFiles) {
50
+ await pool.buildFileMode({
51
+ configPath,
52
+ file,
53
+ modeName,
54
+ featureToggles,
55
+ overrides,
56
+ });
57
+ }
58
+ return;
59
+ }
24
60
  for (const file of inputFiles) {
25
61
  const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName, duplicateSpecBasenames)}`;
26
62
  const invocation = getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles);
27
63
  try {
28
- buildFile(invocation, buildEnv);
64
+ await buildFile(invocation, buildEnv);
29
65
  }
30
66
  catch (error) {
31
67
  const modeLabel = modeName ?? "default";
32
- throw new Error(`Failed to build ${path.basename(file)} in mode ${modeLabel} with ${getBuildStderr(error)}\nBuild command: ${formatInvocation(invocation)}`);
68
+ const stdout = getBuildStdout(error);
69
+ const stderr = getBuildStderr(error);
70
+ const buildCommand = formatInvocation(invocation);
71
+ const kind = overrides.kind ?? "test";
72
+ const crash = persistCrashRecord(config.fuzz.crashDir, {
73
+ kind,
74
+ stage: "build",
75
+ file,
76
+ mode: modeLabel,
77
+ cwd: process.cwd(),
78
+ buildCommand,
79
+ reproCommand: buildCommand,
80
+ error: stderr || stdout || "unknown build error",
81
+ stdout,
82
+ stderr,
83
+ });
84
+ throw new BuildFailureError({
85
+ file,
86
+ mode: modeLabel,
87
+ invocation,
88
+ stdout,
89
+ stderr,
90
+ kind,
91
+ message: `Failed to build ${path.basename(file)} in mode ${modeLabel} with ${stderr || stdout || "unknown build error"}\n` +
92
+ `Build command: ${buildCommand}\n` +
93
+ `Crash log: ${crash.logPath}`,
94
+ });
33
95
  }
34
96
  }
35
97
  }
98
+ let serialBuildWorkerPool = null;
99
+ function getSerialBuildWorkerPool() {
100
+ if (!serialBuildWorkerPool) {
101
+ serialBuildWorkerPool = new BuildWorkerPool(1);
102
+ }
103
+ return serialBuildWorkerPool;
104
+ }
105
+ export async function getBuildInvocationPreview(configPath = DEFAULT_CONFIG_PATH, file, modeName, featureToggles = {}, overrides = {}) {
106
+ const loadedConfig = loadConfig(configPath, false);
107
+ const mode = applyMode(loadedConfig, modeName);
108
+ const config = Object.assign(Object.create(Object.getPrototypeOf(mode.config)), mode.config);
109
+ config.buildOptions = Object.assign(Object.create(Object.getPrototypeOf(mode.config.buildOptions)), mode.config.buildOptions);
110
+ if (overrides.target) {
111
+ config.buildOptions.target = overrides.target;
112
+ }
113
+ if (overrides.args?.length) {
114
+ config.buildOptions.args = [...config.buildOptions.args, ...overrides.args];
115
+ }
116
+ const duplicateSpecBasenames = resolveDuplicateBasenames([file]);
117
+ const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName, duplicateSpecBasenames)}`;
118
+ return getBuildCommand(config, getPkgRunner(), file, outFile, modeName, featureToggles);
119
+ }
36
120
  function hasCustomBuildCommand(config) {
37
121
  return !!config.buildOptions.cmd.trim().length;
38
122
  }
@@ -49,17 +133,41 @@ function getBuildCommand(config, pkgRunner, file, outFile, modeName, featureTogg
49
133
  };
50
134
  }
51
135
  const defaultArgs = getDefaultBuildArgs(config, featureToggles);
52
- const args = ["asc", file, ...userArgs, ...defaultArgs];
136
+ const ascInvocation = resolveAscInvocation(pkgRunner);
137
+ const args = [...ascInvocation.args, file, ...userArgs, ...defaultArgs];
53
138
  if (config.outDir.length) {
54
139
  args.push("-o", outFile);
55
140
  }
56
141
  return {
57
- command: pkgRunner,
142
+ command: ascInvocation.command,
58
143
  args,
144
+ apiArgs: args.slice(1),
145
+ };
146
+ }
147
+ function resolveAscInvocation(pkgRunner) {
148
+ const assemblyscriptPkg = resolveProjectModule("assemblyscript/package.json");
149
+ if (assemblyscriptPkg) {
150
+ const ascPath = path.join(path.dirname(assemblyscriptPkg), "bin", "asc.js");
151
+ if (existsSync(ascPath)) {
152
+ return {
153
+ command: process.execPath,
154
+ args: [ascPath],
155
+ };
156
+ }
157
+ }
158
+ return {
159
+ command: pkgRunner,
160
+ args: ["asc"],
59
161
  };
60
162
  }
61
163
  function getUserBuildArgs(config) {
62
- return config.buildOptions.args.filter((value) => value.length > 0);
164
+ const args = [];
165
+ for (const value of config.buildOptions.args) {
166
+ if (!value.length)
167
+ continue;
168
+ args.push(...tokenizeCommand(value));
169
+ }
170
+ return args;
63
171
  }
64
172
  function expandBuildCommand(template, file, outFile, target, modeName) {
65
173
  const name = path
@@ -92,9 +200,7 @@ function resolveArtifactFileName(file, target, modeName, duplicateSpecBasenames
92
200
  const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
93
201
  return `${stem}.${disambiguator}${ext}`;
94
202
  }
95
- async function resolveDuplicateSpecBasenames(configured) {
96
- const patterns = Array.isArray(configured) ? configured : [configured];
97
- const files = await glob(patterns);
203
+ function resolveDuplicateBasenames(files) {
98
204
  const counts = new Map();
99
205
  for (const file of files) {
100
206
  const base = path.basename(file);
@@ -177,21 +283,96 @@ function ensureDeps(config) {
177
283
  }
178
284
  }
179
285
  }
180
- function buildFile(invocation, env) {
181
- const result = spawnSync(invocation.command, invocation.args, {
182
- stdio: ["ignore", "pipe", "pipe"],
183
- encoding: "utf8",
184
- env,
185
- shell: false,
286
+ async function buildFile(invocation, env) {
287
+ if (process.env.AS_TEST_BUILD_API == "1" && invocation.apiArgs?.length) {
288
+ await buildFileViaApi(invocation.apiArgs, env);
289
+ return;
290
+ }
291
+ await buildFileViaSpawn(invocation, env);
292
+ }
293
+ async function buildFileViaApi(args, env) {
294
+ const stdoutChunks = [];
295
+ const stderrChunks = [];
296
+ const stdout = createMemoryStream((chunk) => {
297
+ stdoutChunks.push(typeof chunk == "string" ? chunk : Buffer.from(chunk).toString("utf8"));
298
+ });
299
+ const stderr = createMemoryStream((chunk) => {
300
+ stderrChunks.push(typeof chunk == "string" ? chunk : Buffer.from(chunk).toString("utf8"));
301
+ });
302
+ const previousEnv = snapshotEnv();
303
+ applyEnv(env);
304
+ try {
305
+ const result = await ascMain(args, { stdout, stderr });
306
+ if (result.error) {
307
+ const error = result.error;
308
+ error.stderr = stderrChunks.join("").trim();
309
+ error.stdout = stdoutChunks.join("").trim();
310
+ throw error;
311
+ }
312
+ }
313
+ finally {
314
+ restoreEnv(previousEnv);
315
+ }
316
+ }
317
+ async function buildFileViaSpawn(invocation, env) {
318
+ await new Promise((resolve, reject) => {
319
+ const child = spawn(invocation.command, invocation.args, {
320
+ stdio: ["ignore", "pipe", "pipe"],
321
+ env,
322
+ shell: false,
323
+ });
324
+ let stdout = "";
325
+ let stderr = "";
326
+ child.stdout?.on("data", (chunk) => {
327
+ stdout += chunk.toString();
328
+ });
329
+ child.stderr?.on("data", (chunk) => {
330
+ stderr += chunk.toString();
331
+ });
332
+ child.on("error", reject);
333
+ child.on("close", (code) => {
334
+ if (code === 0) {
335
+ resolve();
336
+ return;
337
+ }
338
+ const error = new Error(stderr.trim() || stdout.trim() || `command exited with code ${code}`);
339
+ error.stderr = stderr.trim();
340
+ error.stdout = stdout.trim();
341
+ reject(error);
342
+ });
186
343
  });
187
- if (result.error)
188
- throw result.error;
189
- if (result.status !== 0) {
190
- const error = new Error(result.stderr?.trim() ||
191
- result.stdout?.trim() ||
192
- `command exited with code ${result.status}`);
193
- error.stderr = result.stderr ?? "";
194
- throw error;
344
+ }
345
+ function snapshotEnv() {
346
+ return { ...process.env };
347
+ }
348
+ function applyEnv(nextEnv) {
349
+ const keys = new Set([
350
+ ...Object.keys(process.env),
351
+ ...Object.keys(nextEnv),
352
+ ]);
353
+ for (const key of keys) {
354
+ const value = nextEnv[key];
355
+ if (value == undefined) {
356
+ delete process.env[key];
357
+ }
358
+ else {
359
+ process.env[key] = value;
360
+ }
361
+ }
362
+ }
363
+ function restoreEnv(previousEnv) {
364
+ const keys = new Set([
365
+ ...Object.keys(process.env),
366
+ ...Object.keys(previousEnv),
367
+ ]);
368
+ for (const key of keys) {
369
+ const value = previousEnv[key];
370
+ if (value == undefined) {
371
+ delete process.env[key];
372
+ }
373
+ else {
374
+ process.env[key] = value;
375
+ }
195
376
  }
196
377
  }
197
378
  function formatInvocation(invocation) {
@@ -199,6 +380,7 @@ function formatInvocation(invocation) {
199
380
  .map((token) => (/\s/.test(token) ? JSON.stringify(token) : token))
200
381
  .join(" ");
201
382
  }
383
+ export { getBuildCommand, formatInvocation };
202
384
  function getBuildStderr(error) {
203
385
  const err = error;
204
386
  const stderr = err?.stderr;
@@ -215,6 +397,15 @@ function getBuildStderr(error) {
215
397
  const message = typeof err?.message == "string" ? err.message.trim() : "";
216
398
  return message || "unknown error";
217
399
  }
400
+ function getBuildStdout(error) {
401
+ const err = error;
402
+ const stdout = err?.stdout;
403
+ if (typeof stdout == "string")
404
+ return stdout.trim();
405
+ if (stdout instanceof Buffer)
406
+ return stdout.toString("utf8").trim();
407
+ return "";
408
+ }
218
409
  function getDefaultBuildArgs(config, featureToggles) {
219
410
  const buildArgs = [];
220
411
  const tryAsEnabled = resolveTryAsEnabled(featureToggles.tryAs);
@@ -229,7 +420,8 @@ function getDefaultBuildArgs(config, featureToggles) {
229
420
  buildArgs.push("--use", "AS_TEST_TRY_AS=1");
230
421
  }
231
422
  // Should also strip any bindings-enabling from asconfig
232
- if (config.buildOptions.target == "bindings") {
423
+ if (config.buildOptions.target == "bindings" ||
424
+ config.buildOptions.target == "web") {
233
425
  buildArgs.push("--use", "AS_TEST_BINDINGS=1", "--bindings", "raw", "--exportRuntime", "--exportStart", "_start");
234
426
  }
235
427
  else if (config.buildOptions.target == "wasi") {
@@ -240,7 +432,7 @@ function getDefaultBuildArgs(config, featureToggles) {
240
432
  buildArgs.push("--use", "AS_TEST_WASI=1", "--config", wasiShim.configPath);
241
433
  }
242
434
  else {
243
- console.log(`${chalk.bgRed(" ERROR ")}${chalk.dim(":")} could not determine target in config! Set target to 'bindings' or 'wasi'`);
435
+ console.log(`${chalk.bgRed(" ERROR ")}${chalk.dim(":")} could not determine target in config! Set target to 'bindings', 'web', or 'wasi'`);
244
436
  process.exit(1);
245
437
  }
246
438
  return buildArgs;
@@ -252,7 +444,7 @@ function resolveTryAsEnabled(override) {
252
444
  if (override === true && !installed) {
253
445
  throw new Error('try-as feature was enabled, but package "try-as" is not installed');
254
446
  }
255
- return installed;
447
+ return false;
256
448
  }
257
449
  function resolveCoverageEnabled(rawCoverage, override) {
258
450
  if (override != undefined)
@@ -1,4 +1,5 @@
1
1
  export { build } from "./build-core.js";
2
+ export { formatInvocation, getBuildInvocationPreview } from "./build-core.js";
2
3
  export async function executeBuildCommand(rawArgs, configPath, selectedModes, deps) {
3
4
  const commandArgs = deps.resolveCommandArgs(rawArgs, "build");
4
5
  const listFlags = deps.resolveListFlags(rawArgs, "build");
@@ -0,0 +1,306 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import * as path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import { glob } from "glob";
5
+ import { build } from "./build-core.js";
6
+ import { applyMode, loadConfig } from "../util.js";
7
+ import { persistCrashRecord } from "../crash-store.js";
8
+ const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
9
+ const MAGIC = Buffer.from("WIPC");
10
+ const HEADER_SIZE = 9;
11
+ export async function fuzz(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, overrides = {}) {
12
+ const loadedConfig = loadConfig(configPath, false);
13
+ const mode = applyMode(loadedConfig, modeName);
14
+ const config = resolveFuzzConfig(loadedConfig.fuzz, overrides);
15
+ const inputPatterns = resolveFuzzInputPatterns(config.input, selectors);
16
+ const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
17
+ if (!inputFiles.length) {
18
+ throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
19
+ }
20
+ const duplicateBasenames = resolveDuplicateBasenames(inputFiles);
21
+ const results = [];
22
+ for (const file of inputFiles) {
23
+ const buildStartedAt = Date.now();
24
+ await build(configPath, [file], modeName, { coverage: false }, { target: "bindings", args: ["--use", "AS_TEST_FUZZ=1"], kind: "fuzz" });
25
+ const buildFinishedAt = Date.now();
26
+ const buildTime = buildFinishedAt - buildStartedAt;
27
+ results.push(await runFuzzTarget(file, mode.config.outDir, duplicateBasenames, config, buildStartedAt, buildFinishedAt, buildTime, modeName));
28
+ }
29
+ return results;
30
+ }
31
+ function resolveFuzzConfig(raw, overrides) {
32
+ const config = Object.assign({}, raw, overrides);
33
+ if (config.target != "bindings") {
34
+ throw new Error(`fuzz target must be "bindings"; received "${config.target}"`);
35
+ }
36
+ return config;
37
+ }
38
+ async function runFuzzTarget(file, outDir, duplicateBasenames, config, buildStartedAt, buildFinishedAt, buildTime, modeName) {
39
+ const startedAt = Date.now();
40
+ const artifact = resolveArtifactFileName(file, duplicateBasenames, modeName);
41
+ const wasmPath = path.resolve(process.cwd(), outDir, artifact);
42
+ const jsPath = resolveBindingsHelperPath(wasmPath);
43
+ const helper = await import(pathToFileURL(jsPath).href + `?t=${Date.now()}`);
44
+ const binary = readFileSync(wasmPath);
45
+ const module = new WebAssembly.Module(binary);
46
+ let report = null;
47
+ const captured = captureFrames((type, payload, respond) => {
48
+ if (type == 0x02) {
49
+ const event = JSON.parse(payload.toString("utf8"));
50
+ if (String(event.kind ?? "") == "fuzz:config") {
51
+ respond(`${config.runs}\n${config.seed}`);
52
+ }
53
+ else {
54
+ respond("");
55
+ }
56
+ return;
57
+ }
58
+ if (type == 0x03) {
59
+ report = JSON.parse(payload.toString("utf8"));
60
+ }
61
+ });
62
+ try {
63
+ await helper.instantiate(module, {});
64
+ }
65
+ catch (error) {
66
+ const passthrough = captured.restore();
67
+ const crashMessage = error instanceof Error ? (error.stack ?? error.message) : String(error);
68
+ const crash = persistCrashRecord(config.crashDir, {
69
+ kind: "fuzz",
70
+ file,
71
+ mode: modeName ?? "default",
72
+ seed: config.seed,
73
+ error: crashMessage,
74
+ stdout: passthrough.stdout,
75
+ stderr: "",
76
+ });
77
+ return {
78
+ file,
79
+ target: path.basename(file),
80
+ modeName: modeName ?? "default",
81
+ runs: config.runs,
82
+ crashes: 1,
83
+ crashFiles: [crash.jsonPath],
84
+ seed: config.seed,
85
+ time: Date.now() - startedAt,
86
+ buildTime,
87
+ buildStartedAt,
88
+ buildFinishedAt,
89
+ fuzzers: [],
90
+ };
91
+ }
92
+ const passthrough = captured.restore();
93
+ if (!report?.fuzzers) {
94
+ const crash = persistCrashRecord(config.crashDir, {
95
+ kind: "fuzz",
96
+ file,
97
+ mode: modeName ?? "default",
98
+ seed: config.seed,
99
+ error: `missing fuzz report payload from ${path.basename(file)}`,
100
+ stdout: passthrough.stdout,
101
+ stderr: "",
102
+ });
103
+ return {
104
+ file,
105
+ target: path.basename(file),
106
+ modeName: modeName ?? "default",
107
+ runs: config.runs,
108
+ crashes: 1,
109
+ crashFiles: [crash.jsonPath],
110
+ seed: config.seed,
111
+ time: Date.now() - startedAt,
112
+ buildTime,
113
+ buildStartedAt,
114
+ buildFinishedAt,
115
+ fuzzers: [],
116
+ };
117
+ }
118
+ return {
119
+ file,
120
+ target: path.basename(file),
121
+ modeName: modeName ?? "default",
122
+ runs: report.fuzzers.reduce((sum, item) => sum + item.runs, 0),
123
+ crashes: report.fuzzers.reduce((sum, item) => sum + item.crashed, 0),
124
+ crashFiles: [],
125
+ seed: config.seed,
126
+ time: Date.now() - startedAt,
127
+ buildTime,
128
+ buildStartedAt,
129
+ buildFinishedAt,
130
+ fuzzers: report.fuzzers,
131
+ };
132
+ }
133
+ function captureFrames(onFrame) {
134
+ const originalWrite = process.stdout.write.bind(process.stdout);
135
+ const originalRead = typeof process.stdin.read == "function"
136
+ ? process.stdin.read.bind(process.stdin)
137
+ : null;
138
+ let buffer = Buffer.alloc(0);
139
+ let passthrough = Buffer.alloc(0);
140
+ let replies = Buffer.alloc(0);
141
+ function encodeReply(body) {
142
+ const payload = Buffer.from(body, "utf8");
143
+ const header = Buffer.alloc(HEADER_SIZE);
144
+ MAGIC.copy(header, 0);
145
+ header.writeUInt8(0x02, 4);
146
+ header.writeUInt32LE(payload.length, 5);
147
+ return Buffer.concat([header, payload]);
148
+ }
149
+ function dequeueReply(length) {
150
+ const available = Math.min(length, replies.length);
151
+ const view = replies.subarray(0, available);
152
+ replies = replies.subarray(available);
153
+ return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
154
+ }
155
+ process.stdout.write = ((chunk, ...args) => {
156
+ if (!(chunk instanceof ArrayBuffer) && !Buffer.isBuffer(chunk)) {
157
+ return originalWrite(chunk, ...args);
158
+ }
159
+ const incoming = Buffer.from(chunk);
160
+ buffer = Buffer.concat([buffer, incoming]);
161
+ while (true) {
162
+ const index = buffer.indexOf(MAGIC);
163
+ if (index == -1) {
164
+ if (buffer.length) {
165
+ passthrough = Buffer.concat([passthrough, buffer]);
166
+ originalWrite(buffer);
167
+ buffer = Buffer.alloc(0);
168
+ }
169
+ return true;
170
+ }
171
+ if (index > 0) {
172
+ const raw = buffer.subarray(0, index);
173
+ passthrough = Buffer.concat([passthrough, raw]);
174
+ originalWrite(raw);
175
+ buffer = buffer.subarray(index);
176
+ }
177
+ if (buffer.length < HEADER_SIZE)
178
+ return true;
179
+ const type = buffer.readUInt8(4);
180
+ const length = buffer.readUInt32LE(5);
181
+ const frameSize = HEADER_SIZE + length;
182
+ if (buffer.length < frameSize)
183
+ return true;
184
+ const payload = buffer.subarray(HEADER_SIZE, frameSize);
185
+ buffer = buffer.subarray(frameSize);
186
+ onFrame(type, payload, (body) => {
187
+ replies = Buffer.concat([replies, encodeReply(body)]);
188
+ });
189
+ }
190
+ });
191
+ process.stdin.read = ((size) => {
192
+ const max = Number(size ?? 0);
193
+ if (max > 0 && replies.length) {
194
+ return dequeueReply(max);
195
+ }
196
+ if (originalRead) {
197
+ return originalRead(size);
198
+ }
199
+ return null;
200
+ });
201
+ return {
202
+ restore() {
203
+ process.stdout.write = originalWrite;
204
+ if (originalRead) {
205
+ process.stdin.read = originalRead;
206
+ }
207
+ return {
208
+ stdout: passthrough.toString("utf8"),
209
+ };
210
+ },
211
+ };
212
+ }
213
+ function resolveFuzzInputPatterns(configured, selectors) {
214
+ const configuredInputs = Array.isArray(configured)
215
+ ? configured
216
+ : [configured];
217
+ if (!selectors.length)
218
+ return configuredInputs;
219
+ const patterns = new Set();
220
+ for (const selector of expandSelectors(selectors)) {
221
+ if (!selector)
222
+ continue;
223
+ if (isBareSelector(selector)) {
224
+ const base = selector.replace(/\.fuzz\.ts$/, "").replace(/\.ts$/, "");
225
+ for (const configuredInput of configuredInputs) {
226
+ patterns.add(path.join(path.dirname(configuredInput), `${base}.fuzz.ts`));
227
+ }
228
+ continue;
229
+ }
230
+ patterns.add(selector);
231
+ }
232
+ return [...patterns];
233
+ }
234
+ function resolveArtifactFileName(file, duplicateBasenames, modeName) {
235
+ const base = path
236
+ .basename(file)
237
+ .replace(/\.spec\.ts$/, "")
238
+ .replace(/\.ts$/, "");
239
+ const legacy = !modeName
240
+ ? `${path.basename(file).replace(".ts", ".wasm")}`
241
+ : `${base}.${modeName}.bindings.wasm`;
242
+ if (!duplicateBasenames.has(path.basename(file))) {
243
+ return legacy;
244
+ }
245
+ const disambiguator = resolveDisambiguator(file);
246
+ if (!disambiguator.length) {
247
+ return legacy;
248
+ }
249
+ const ext = path.extname(legacy);
250
+ const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
251
+ return `${stem}.${disambiguator}${ext}`;
252
+ }
253
+ function resolveDuplicateBasenames(files) {
254
+ const counts = new Map();
255
+ for (const file of files) {
256
+ const base = path.basename(file);
257
+ counts.set(base, (counts.get(base) ?? 0) + 1);
258
+ }
259
+ const duplicates = new Set();
260
+ for (const [base, count] of counts) {
261
+ if (count > 1)
262
+ duplicates.add(base);
263
+ }
264
+ return duplicates;
265
+ }
266
+ function resolveDisambiguator(file) {
267
+ const relDir = path.dirname(path.relative(process.cwd(), file));
268
+ if (!relDir.length || relDir == ".")
269
+ return "";
270
+ return relDir
271
+ .replace(/[\\/]+/g, "__")
272
+ .replace(/[^A-Za-z0-9._-]/g, "_")
273
+ .replace(/^_+|_+$/g, "");
274
+ }
275
+ function resolveBindingsHelperPath(wasmPath) {
276
+ const bindingsPath = wasmPath.replace(/\.wasm$/, ".bindings.js");
277
+ if (existsSync(bindingsPath))
278
+ return bindingsPath;
279
+ const directPath = wasmPath.replace(/\.wasm$/, ".js");
280
+ if (existsSync(directPath))
281
+ return directPath;
282
+ return bindingsPath;
283
+ }
284
+ function expandSelectors(selectors) {
285
+ const expanded = [];
286
+ for (const selector of selectors) {
287
+ if (selector.includes(",") &&
288
+ !selector.includes("/") &&
289
+ !selector.includes("\\") &&
290
+ !/[*?[\]{}]/.test(selector)) {
291
+ for (const token of selector.split(",")) {
292
+ const trimmed = token.trim();
293
+ if (trimmed.length)
294
+ expanded.push(trimmed);
295
+ }
296
+ continue;
297
+ }
298
+ expanded.push(selector);
299
+ }
300
+ return expanded;
301
+ }
302
+ function isBareSelector(selector) {
303
+ return (!selector.includes("/") &&
304
+ !selector.includes("\\") &&
305
+ !/[*?[\]{}]/.test(selector));
306
+ }
@@ -0,0 +1,10 @@
1
+ export async function executeFuzzCommand(rawArgs, configPath, selectedModes, deps) {
2
+ const commandArgs = deps.resolveCommandArgs(rawArgs, "fuzz");
3
+ const listFlags = deps.resolveListFlags(rawArgs, "fuzz");
4
+ const modeTargets = deps.resolveExecutionModes(configPath, selectedModes);
5
+ if (listFlags.list || listFlags.listModes) {
6
+ await deps.listExecutionPlan("fuzz", configPath, commandArgs, modeTargets, listFlags);
7
+ return;
8
+ }
9
+ await deps.runFuzzModes(configPath, commandArgs, modeTargets, rawArgs);
10
+ }