as-test 1.0.13 → 1.0.15

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 2026-05-04 - v1.0.15
4
+
5
+ - fix: path resolving
6
+ -
7
+ ## 2026-05-04 - v1.0.14
8
+
9
+ ### Fuzzing
10
+
11
+ - feat: add `FuzzSeed` helpers for `i8`, `u8`, `i16`, `u16`, `i64`, `u64`, and `bool()`.
12
+ - feat: make integer `FuzzSeed` helpers default to the full range of their target type when no options are provided, instead of collapsing to `0`.
13
+ - perf: add unchecked full-range fast paths for default integer seed generation while keeping explicit user-provided ranges validated.
14
+
3
15
  ## 2025-05-03 - v1.0.13
4
16
 
5
17
  - feat: add `--fuzzer` / `--fuzzers` filtering for `ast fuzz` and `ast test --fuzz`, accept `--suite` / `--suites` as fuzz aliases, and include target-specific repro commands in fuzz failure output.
package/README.md CHANGED
@@ -365,6 +365,36 @@ If you want to keep more than one runtime around, use modes:
365
365
  }
366
366
  ```
367
367
 
368
+ Modes can also be full config objects. That means a mode can override fuzzing, input globs, output aliases, runtime, build flags, and the rest of the normal config surface:
369
+
370
+ ```json
371
+ {
372
+ "modes": {
373
+ "web": {
374
+ "fuzz": {
375
+ "input": ["./assembly/__fuzz__/web/*.fuzz.ts"],
376
+ "runs": 200
377
+ },
378
+ "runOptions": {
379
+ "runtime": {
380
+ "browser": "chromium"
381
+ }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ ```
387
+
388
+ If you prefer to keep one mode in a separate file, point the mode directly at that config file:
389
+
390
+ ```json
391
+ {
392
+ "modes": {
393
+ "simd": "./as-test.config.simd.json"
394
+ }
395
+ }
396
+ ```
397
+
368
398
  Run a specific mode with:
369
399
 
370
400
  ```bash
@@ -294,11 +294,43 @@
294
294
  },
295
295
  "modes": {
296
296
  "type": "object",
297
- "description": "Named build/run modes. Each mode can override target/args/runtime/env and artifact directories.",
297
+ "description": "Named build/run modes. Each mode can be a config object or a path to another as-test config file.",
298
298
  "additionalProperties": {
299
- "type": "object",
300
- "additionalProperties": false,
301
- "properties": {
299
+ "oneOf": [
300
+ {
301
+ "type": "string",
302
+ "description": "Path to another as-test config file used as the selected mode's override config."
303
+ },
304
+ {
305
+ "type": "object",
306
+ "additionalProperties": false,
307
+ "properties": {
308
+ "$schema": {
309
+ "type": "string"
310
+ },
311
+ "input": {
312
+ "type": "array",
313
+ "items": {
314
+ "type": "string"
315
+ }
316
+ },
317
+ "output": {
318
+ "oneOf": [
319
+ {
320
+ "type": "string"
321
+ },
322
+ {
323
+ "type": "object",
324
+ "additionalProperties": false,
325
+ "properties": {
326
+ "build": { "type": "string" },
327
+ "logs": { "type": "string" },
328
+ "coverage": { "type": "string" },
329
+ "snapshots": { "type": "string" }
330
+ }
331
+ }
332
+ ]
333
+ },
302
334
  "outDir": {
303
335
  "type": "string",
304
336
  "description": "Mode-specific build output directory. If omitted, defaults to <outDir>/<mode-name>."
@@ -339,6 +371,38 @@
339
371
  }
340
372
  ]
341
373
  },
374
+ "fuzz": {
375
+ "type": "object",
376
+ "additionalProperties": false,
377
+ "description": "Mode-specific fuzz overrides applied before running `ast fuzz` or `ast test --fuzz` in this mode.",
378
+ "properties": {
379
+ "input": {
380
+ "type": "array",
381
+ "items": {
382
+ "type": "string"
383
+ }
384
+ },
385
+ "runs": {
386
+ "type": "number"
387
+ },
388
+ "seed": {
389
+ "type": "number"
390
+ },
391
+ "maxInputBytes": {
392
+ "type": "number"
393
+ },
394
+ "target": {
395
+ "type": "string",
396
+ "enum": ["bindings"]
397
+ },
398
+ "corpusDir": {
399
+ "type": "string"
400
+ },
401
+ "crashDir": {
402
+ "type": "string"
403
+ }
404
+ }
405
+ },
342
406
  "buildOptions": {
343
407
  "type": "object",
344
408
  "additionalProperties": false,
@@ -469,7 +533,9 @@
469
533
  }
470
534
  ]
471
535
  }
472
- }
536
+ }
537
+ }
538
+ ]
473
539
  },
474
540
  "default": {}
475
541
  },
@@ -45,8 +45,16 @@ declare module "as-test" {
45
45
  }
46
46
 
47
47
  export interface FuzzSeed {
48
+ boolean(): boolean;
49
+ bool(): boolean;
50
+ i8(options?: IntellisenseIntegerOptions): number;
51
+ u8(options?: IntellisenseIntegerOptions): number;
52
+ i16(options?: IntellisenseIntegerOptions): number;
53
+ u16(options?: IntellisenseIntegerOptions): number;
48
54
  i32(options?: IntellisenseIntegerOptions): number;
49
55
  u32(options?: IntellisenseIntegerOptions): number;
56
+ i64(options?: IntellisenseIntegerOptions): number;
57
+ u64(options?: IntellisenseIntegerOptions): number;
50
58
  f32(options?: IntellisenseFloatOptions): number;
51
59
  f64(options?: IntellisenseFloatOptions): number;
52
60
  bytes(options?: IntellisenseBytesOptions): Uint8Array;
@@ -34,13 +34,20 @@ export class ArrayOptions {
34
34
  max: i32 = 16;
35
35
  }
36
36
 
37
- const DEFAULT_I32_OPTIONS = new IntegerOptions<i32>();
38
- const DEFAULT_U32_OPTIONS = new IntegerOptions<u32>();
39
37
  const DEFAULT_F32_OPTIONS = new FloatOptions<f32>();
40
38
  const DEFAULT_F64_OPTIONS = new FloatOptions<f64>();
41
39
  const DEFAULT_STRING_OPTIONS = new StringOptions();
42
40
  const DEFAULT_BYTES_OPTIONS = new BytesOptions();
43
41
  const DEFAULT_ARRAY_OPTIONS = new ArrayOptions();
42
+ const I64_SIGN_MASK: u64 = 0x8000000000000000;
43
+ const EMPTY_I8_EXCLUDE: i8[] = [];
44
+ const EMPTY_U8_EXCLUDE: u8[] = [];
45
+ const EMPTY_I16_EXCLUDE: i16[] = [];
46
+ const EMPTY_U16_EXCLUDE: u16[] = [];
47
+ const EMPTY_I32_EXCLUDE: i32[] = [];
48
+ const EMPTY_U32_EXCLUDE: u32[] = [];
49
+ const EMPTY_I64_EXCLUDE: i64[] = [];
50
+ const EMPTY_U64_EXCLUDE: u64[] = [];
44
51
 
45
52
  export class FuzzSeed {
46
53
  private state: u32;
@@ -61,19 +68,99 @@ export class FuzzSeed {
61
68
  return (this.nextU32() & 1) == 1;
62
69
  }
63
70
 
71
+ bool(): bool {
72
+ return this.boolean();
73
+ }
74
+
64
75
  pick<T>(values: T[]): T {
65
76
  if (!values.length) panic();
66
77
  return unchecked(values[this.nextRange(0, values.length - 1)]);
67
78
  }
68
79
 
80
+ i8(options: IntegerOptions<i8> | null = null): i8 {
81
+ if (options == null) {
82
+ return this.nextI8InRange(i8.MIN_VALUE, i8.MAX_VALUE, EMPTY_I8_EXCLUDE, false);
83
+ }
84
+ return this.nextI8InRange(options.min, options.max, options.exclude, true);
85
+ }
86
+
87
+ u8(options: IntegerOptions<u8> | null = null): u8 {
88
+ if (options == null) {
89
+ return this.nextU8InRange(u8.MIN_VALUE, u8.MAX_VALUE, EMPTY_U8_EXCLUDE, false);
90
+ }
91
+ return this.nextU8InRange(options.min, options.max, options.exclude, true);
92
+ }
93
+
94
+ i16(options: IntegerOptions<i16> | null = null): i16 {
95
+ if (options == null) {
96
+ return this.nextI16InRange(
97
+ i16.MIN_VALUE,
98
+ i16.MAX_VALUE,
99
+ EMPTY_I16_EXCLUDE,
100
+ false,
101
+ );
102
+ }
103
+ return this.nextI16InRange(options.min, options.max, options.exclude, true);
104
+ }
105
+
106
+ u16(options: IntegerOptions<u16> | null = null): u16 {
107
+ if (options == null) {
108
+ return this.nextU16InRange(
109
+ u16.MIN_VALUE,
110
+ u16.MAX_VALUE,
111
+ EMPTY_U16_EXCLUDE,
112
+ false,
113
+ );
114
+ }
115
+ return this.nextU16InRange(options.min, options.max, options.exclude, true);
116
+ }
117
+
69
118
  i32(options: IntegerOptions<i32> | null = null): i32 {
70
- const config = options != null ? options : DEFAULT_I32_OPTIONS;
71
- return this.nextI32InRange(config.min, config.max, config.exclude);
119
+ if (options == null) {
120
+ return this.nextI32InRange(
121
+ i32.MIN_VALUE,
122
+ i32.MAX_VALUE,
123
+ EMPTY_I32_EXCLUDE,
124
+ false,
125
+ );
126
+ }
127
+ return this.nextI32InRange(options.min, options.max, options.exclude, true);
72
128
  }
73
129
 
74
130
  u32(options: IntegerOptions<u32> | null = null): u32 {
75
- const config = options != null ? options : DEFAULT_U32_OPTIONS;
76
- return this.nextU32InRange(config.min, config.max, config.exclude);
131
+ if (options == null) {
132
+ return this.nextU32InRange(
133
+ u32.MIN_VALUE,
134
+ u32.MAX_VALUE,
135
+ EMPTY_U32_EXCLUDE,
136
+ false,
137
+ );
138
+ }
139
+ return this.nextU32InRange(options.min, options.max, options.exclude, true);
140
+ }
141
+
142
+ i64(options: IntegerOptions<i64> | null = null): i64 {
143
+ if (options == null) {
144
+ return this.nextI64InRange(
145
+ i64.MIN_VALUE,
146
+ i64.MAX_VALUE,
147
+ EMPTY_I64_EXCLUDE,
148
+ false,
149
+ );
150
+ }
151
+ return this.nextI64InRange(options.min, options.max, options.exclude, true);
152
+ }
153
+
154
+ u64(options: IntegerOptions<u64> | null = null): u64 {
155
+ if (options == null) {
156
+ return this.nextU64InRange(
157
+ u64.MIN_VALUE,
158
+ u64.MAX_VALUE,
159
+ EMPTY_U64_EXCLUDE,
160
+ false,
161
+ );
162
+ }
163
+ return this.nextU64InRange(options.min, options.max, options.exclude, true);
77
164
  }
78
165
 
79
166
  f32(options: FloatOptions<f32> | null = null): f32 {
@@ -201,8 +288,16 @@ export class FuzzSeed {
201
288
  return 0;
202
289
  }
203
290
 
204
- private nextI32InRange(min: i32, max: i32, exclude: i32[]): i32 {
205
- if (max < min) panic();
291
+ private nextI32InRange(
292
+ min: i32,
293
+ max: i32,
294
+ exclude: i32[],
295
+ validateRange: bool = true,
296
+ ): i32 {
297
+ if (validateRange && max < min) panic();
298
+ if (!validateRange && min == i32.MIN_VALUE && max == i32.MAX_VALUE) {
299
+ return <i32>this.nextU32();
300
+ }
206
301
  if (!exclude.length) {
207
302
  return max <= min ? min : min + <i32>(this.nextU32() % <u32>(max - min + 1));
208
303
  }
@@ -215,8 +310,120 @@ export class FuzzSeed {
215
310
  return min;
216
311
  }
217
312
 
218
- private nextU32InRange(min: u32, max: u32, exclude: u32[]): u32 {
219
- if (max < min) panic();
313
+ private nextI8InRange(
314
+ min: i8,
315
+ max: i8,
316
+ exclude: i8[],
317
+ validateRange: bool = true,
318
+ ): i8 {
319
+ if (validateRange && max < min) panic();
320
+ if (!validateRange && min == i8.MIN_VALUE && max == i8.MAX_VALUE) {
321
+ return <i8>this.nextU32();
322
+ }
323
+ const left = <i32>min;
324
+ const right = <i32>max;
325
+ if (!exclude.length) {
326
+ return <i8>(
327
+ right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
328
+ );
329
+ }
330
+ for (let attempts = 0; attempts < 1024; attempts++) {
331
+ const value = <i8>(
332
+ right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
333
+ );
334
+ if (!containsValue<i8>(exclude, value)) return value;
335
+ }
336
+ panic();
337
+ return min;
338
+ }
339
+
340
+ private nextU8InRange(
341
+ min: u8,
342
+ max: u8,
343
+ exclude: u8[],
344
+ validateRange: bool = true,
345
+ ): u8 {
346
+ if (validateRange && max < min) panic();
347
+ if (!validateRange && min == u8.MIN_VALUE && max == u8.MAX_VALUE) {
348
+ return <u8>this.nextU32();
349
+ }
350
+ const left = <u32>min;
351
+ const right = <u32>max;
352
+ if (!exclude.length) {
353
+ return <u8>(right <= left ? left : left + (this.nextU32() % (right - left + 1)));
354
+ }
355
+ for (let attempts = 0; attempts < 1024; attempts++) {
356
+ const value = <u8>(
357
+ right <= left ? left : left + (this.nextU32() % (right - left + 1))
358
+ );
359
+ if (!containsValue<u8>(exclude, value)) return value;
360
+ }
361
+ panic();
362
+ return min;
363
+ }
364
+
365
+ private nextI16InRange(
366
+ min: i16,
367
+ max: i16,
368
+ exclude: i16[],
369
+ validateRange: bool = true,
370
+ ): i16 {
371
+ if (validateRange && max < min) panic();
372
+ if (!validateRange && min == i16.MIN_VALUE && max == i16.MAX_VALUE) {
373
+ return <i16>this.nextU32();
374
+ }
375
+ const left = <i32>min;
376
+ const right = <i32>max;
377
+ if (!exclude.length) {
378
+ return <i16>(
379
+ right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
380
+ );
381
+ }
382
+ for (let attempts = 0; attempts < 1024; attempts++) {
383
+ const value = <i16>(
384
+ right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
385
+ );
386
+ if (!containsValue<i16>(exclude, value)) return value;
387
+ }
388
+ panic();
389
+ return min;
390
+ }
391
+
392
+ private nextU16InRange(
393
+ min: u16,
394
+ max: u16,
395
+ exclude: u16[],
396
+ validateRange: bool = true,
397
+ ): u16 {
398
+ if (validateRange && max < min) panic();
399
+ if (!validateRange && min == u16.MIN_VALUE && max == u16.MAX_VALUE) {
400
+ return <u16>this.nextU32();
401
+ }
402
+ const left = <u32>min;
403
+ const right = <u32>max;
404
+ if (!exclude.length) {
405
+ return <u16>(right <= left ? left : left + (this.nextU32() % (right - left + 1)));
406
+ }
407
+ for (let attempts = 0; attempts < 1024; attempts++) {
408
+ const value = <u16>(
409
+ right <= left ? left : left + (this.nextU32() % (right - left + 1))
410
+ );
411
+ if (!containsValue<u16>(exclude, value)) return value;
412
+ }
413
+ panic();
414
+ return min;
415
+ }
416
+
417
+ private nextU32InRange(
418
+ min: u32,
419
+ max: u32,
420
+ exclude: u32[],
421
+ validateRange: bool = true,
422
+ ): u32 {
423
+ if (validateRange && max < min) panic();
424
+ if (!validateRange && min == u32.MIN_VALUE && max == u32.MAX_VALUE) {
425
+ return this.nextU32();
426
+ }
220
427
  if (!exclude.length) {
221
428
  return max <= min ? min : min + (this.nextU32() % (max - min + 1));
222
429
  }
@@ -228,6 +435,52 @@ export class FuzzSeed {
228
435
  return min;
229
436
  }
230
437
 
438
+ private nextI64InRange(
439
+ min: i64,
440
+ max: i64,
441
+ exclude: i64[],
442
+ validateRange: bool = true,
443
+ ): i64 {
444
+ if (validateRange && max < min) panic();
445
+ if (!validateRange && min == i64.MIN_VALUE && max == i64.MAX_VALUE) {
446
+ return <i64>this.nextU64();
447
+ }
448
+ const left = this.toOrderedU64(min);
449
+ const right = this.toOrderedU64(max);
450
+ if (!exclude.length) {
451
+ return this.fromOrderedU64(
452
+ left + this.nextU64Offset(left, right),
453
+ );
454
+ }
455
+ for (let attempts = 0; attempts < 1024; attempts++) {
456
+ const value = this.fromOrderedU64(left + this.nextU64Offset(left, right));
457
+ if (!containsValue<i64>(exclude, value)) return value;
458
+ }
459
+ panic();
460
+ return min;
461
+ }
462
+
463
+ private nextU64InRange(
464
+ min: u64,
465
+ max: u64,
466
+ exclude: u64[],
467
+ validateRange: bool = true,
468
+ ): u64 {
469
+ if (validateRange && max < min) panic();
470
+ if (!validateRange && min == u64.MIN_VALUE && max == u64.MAX_VALUE) {
471
+ return this.nextU64();
472
+ }
473
+ if (!exclude.length) {
474
+ return min + this.nextU64Offset(min, max);
475
+ }
476
+ for (let attempts = 0; attempts < 1024; attempts++) {
477
+ const value = min + this.nextU64Offset(min, max);
478
+ if (!containsValue<u64>(exclude, value)) return value;
479
+ }
480
+ panic();
481
+ return min;
482
+ }
483
+
231
484
  private nextF64InRange<T>(min: T, max: T, exclude: T[]): f64 {
232
485
  const left = <f64>min;
233
486
  const right = <f64>max;
@@ -268,6 +521,20 @@ export class FuzzSeed {
268
521
  const lo = <u64>this.nextU32();
269
522
  return (hi << 32) | lo;
270
523
  }
524
+
525
+ private nextU64Offset(min: u64, max: u64): u64 {
526
+ if (max <= min) return 0;
527
+ if (min == 0 && max == u64.MAX_VALUE) return this.nextU64();
528
+ return this.nextU64() % (max - min + 1);
529
+ }
530
+
531
+ private toOrderedU64(value: i64): u64 {
532
+ return <u64>value ^ I64_SIGN_MASK;
533
+ }
534
+
535
+ private fromOrderedU64(value: u64): i64 {
536
+ return <i64>(value ^ I64_SIGN_MASK);
537
+ }
271
538
  }
272
539
 
273
540
  const ASCII_ALPHABET: i32[] = rangeChars(32, 126);
@@ -20,8 +20,8 @@ class BuildFailureError extends Error {
20
20
  this.kind = args.kind;
21
21
  }
22
22
  }
23
- export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}, overrides = {}) {
24
- const loadedConfig = loadConfig(configPath, false);
23
+ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}, overrides = {}, resolvedConfig) {
24
+ const loadedConfig = resolvedConfig ?? loadConfig(configPath, false);
25
25
  const mode = applyMode(loadedConfig, modeName);
26
26
  const config = Object.assign(Object.create(Object.getPrototypeOf(mode.config)), mode.config);
27
27
  config.buildOptions = Object.assign(Object.create(Object.getPrototypeOf(mode.config.buildOptions)), mode.config.buildOptions);
@@ -44,7 +44,9 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
44
44
  ...config.buildOptions.env,
45
45
  AS_TEST_COVERAGE_ENABLED: coverageEnabled ? "1" : "0",
46
46
  };
47
- if (!process.env.AS_TEST_BUILD_API && !hasCustomBuildCommand(config)) {
47
+ if (!resolvedConfig &&
48
+ !process.env.AS_TEST_BUILD_API &&
49
+ !hasCustomBuildCommand(config)) {
48
50
  const pool = getSerialBuildWorkerPool();
49
51
  for (const file of inputFiles) {
50
52
  await pool.buildFileMode({
@@ -12,7 +12,8 @@ const MAX_DEFAULT_SEED = 0x7fffffff;
12
12
  export async function fuzz(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, overrides = {}, fuzzerSelectors = []) {
13
13
  const loadedConfig = loadConfig(configPath, false);
14
14
  const mode = applyMode(loadedConfig, modeName);
15
- const config = resolveFuzzConfig(loadedConfig.fuzz, overrides);
15
+ const activeConfig = mode.config;
16
+ const config = resolveFuzzConfig(activeConfig.fuzz, overrides);
16
17
  const inputPatterns = resolveFuzzInputPatterns(config.input, selectors);
17
18
  const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
18
19
  if (!inputFiles.length) {
@@ -22,10 +23,10 @@ export async function fuzz(configPath = DEFAULT_CONFIG_PATH, selectors = [], mod
22
23
  const results = [];
23
24
  for (const file of inputFiles) {
24
25
  const buildStartedAt = Date.now();
25
- await build(configPath, [file], modeName, { coverage: false }, { target: "bindings", args: ["--use", "AS_TEST_FUZZ=1"], kind: "fuzz" });
26
+ await build(configPath, [file], modeName, { coverage: false }, { target: "bindings", args: ["--use", "AS_TEST_FUZZ=1"], kind: "fuzz" }, loadedConfig);
26
27
  const buildFinishedAt = Date.now();
27
28
  const buildTime = buildFinishedAt - buildStartedAt;
28
- results.push(await runFuzzTarget(file, mode.config.outDir, duplicateBasenames, config, fuzzerSelectors, buildStartedAt, buildFinishedAt, buildTime, modeName));
29
+ results.push(await runFuzzTarget(file, activeConfig.outDir, duplicateBasenames, config, fuzzerSelectors, buildStartedAt, buildFinishedAt, buildTime, modeName));
29
30
  }
30
31
  return results;
31
32
  }
package/bin/index.js CHANGED
@@ -1390,7 +1390,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1390
1390
  if (!fuzzEnabled) {
1391
1391
  throw await buildNoTestFilesMatchedError(configPath, selectors);
1392
1392
  }
1393
- const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
1393
+ const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors, modes);
1394
1394
  if (!fuzzFiles.length) {
1395
1395
  throw await buildNoTestFilesMatchedError(configPath, selectors, true);
1396
1396
  }
@@ -1514,7 +1514,7 @@ async function runFuzzModes(configPath, selectors, fuzzerSelectors, modes, rawAr
1514
1514
  const overrides = resolveFuzzOverrides(rawArgs, "fuzz");
1515
1515
  const parallelSettings = resolveFuzzParallelJobs(rawArgs);
1516
1516
  const clean = rawArgs.includes("--clean");
1517
- const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
1517
+ const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors, modes);
1518
1518
  const { jobs, buildJobs, runJobs } = resolveEffectiveParallelJobs(parallelSettings, fuzzFiles.length);
1519
1519
  if (jobs > 1) {
1520
1520
  const results = await runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, modes, overrides, jobs, buildJobs, runJobs, clean);
@@ -1798,7 +1798,7 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
1798
1798
  if (!fuzzEnabled) {
1799
1799
  throw await buildNoTestFilesMatchedError(configPath, selectors);
1800
1800
  }
1801
- const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors);
1801
+ const fuzzFiles = await resolveSelectedFuzzFiles(configPath, selectors, modes);
1802
1802
  if (!fuzzFiles.length) {
1803
1803
  throw await buildNoTestFilesMatchedError(configPath, selectors, true);
1804
1804
  }
@@ -1918,7 +1918,11 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
1918
1918
  return failed;
1919
1919
  }
1920
1920
  async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelectors, modes, overrides, jobs, buildJobs, runJobs, clean) {
1921
- const files = await resolveSelectedFuzzFiles(configPath, selectors);
1921
+ const filesByMode = new Map();
1922
+ for (const modeName of modes) {
1923
+ filesByMode.set(modeName, await resolveSelectedFuzzFiles(configPath, selectors, [modeName]));
1924
+ }
1925
+ const files = [...new Set([...filesByMode.values()].flat())].sort((a, b) => a.localeCompare(b));
1922
1926
  if (!files.length) {
1923
1927
  throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
1924
1928
  }
@@ -1929,6 +1933,8 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelecto
1929
1933
  const token = renderQueuedFileStart(queueDisplay, path.basename(file));
1930
1934
  const fileResults = [];
1931
1935
  for (const modeName of modes) {
1936
+ if (!(filesByMode.get(modeName)?.includes(file) ?? false))
1937
+ continue;
1932
1938
  const modeResults = await fuzz(configPath, [file], modeName, overrides, fuzzerSelectors);
1933
1939
  fileResults.push(...modeResults);
1934
1940
  }
@@ -1942,19 +1948,22 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelecto
1942
1948
  return ordered.flat();
1943
1949
  }
1944
1950
  async function runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, overrides, reporter) {
1945
- const files = await resolveSelectedFuzzFiles(configPath, selectors);
1946
- if (!files.length) {
1947
- throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
1948
- }
1949
1951
  const results = [];
1950
- for (const file of files) {
1951
- const fileResults = [];
1952
- for (const modeName of modes) {
1952
+ for (const modeName of modes) {
1953
+ const files = await resolveSelectedFuzzFiles(configPath, selectors, [modeName]);
1954
+ if (!files.length) {
1955
+ continue;
1956
+ }
1957
+ for (const file of files) {
1958
+ const fileResults = [];
1953
1959
  const modeResults = await fuzz(configPath, [file], modeName, overrides, fuzzerSelectors);
1954
1960
  fileResults.push(...modeResults);
1955
1961
  results.push(...modeResults);
1962
+ reporter?.onFuzzFileComplete?.({ file, results: fileResults });
1956
1963
  }
1957
- reporter?.onFuzzFileComplete?.({ file, results: fileResults });
1964
+ }
1965
+ if (!results.length) {
1966
+ throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
1958
1967
  }
1959
1968
  return results;
1960
1969
  }
@@ -2214,13 +2223,21 @@ async function resolveSelectedFiles(configPath, selectors, warn = true) {
2214
2223
  const specs = matches.filter((file) => file.endsWith(".spec.ts"));
2215
2224
  return [...new Set(specs)].sort((a, b) => a.localeCompare(b));
2216
2225
  }
2217
- async function resolveSelectedFuzzFiles(configPath, selectors) {
2226
+ async function resolveSelectedFuzzFiles(configPath, selectors, modes = [undefined]) {
2218
2227
  const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
2219
- const config = loadConfig(resolvedConfigPath, false);
2220
- const patterns = resolveFuzzPatterns(config.fuzz.input, selectors);
2221
- const matches = await glob(patterns);
2222
- const fuzzFiles = matches.filter((file) => file.endsWith(".fuzz.ts"));
2223
- return [...new Set(fuzzFiles)].sort((a, b) => a.localeCompare(b));
2228
+ const files = new Set();
2229
+ for (const modeName of modes) {
2230
+ const loaded = loadConfig(resolvedConfigPath, false);
2231
+ const applied = applyMode(loaded, modeName);
2232
+ const config = applied.config;
2233
+ const patterns = resolveFuzzPatterns(config.fuzz.input, selectors);
2234
+ const matches = await glob(patterns);
2235
+ for (const file of matches) {
2236
+ if (file.endsWith(".fuzz.ts"))
2237
+ files.add(file);
2238
+ }
2239
+ }
2240
+ return [...files].sort((a, b) => a.localeCompare(b));
2224
2241
  }
2225
2242
  async function resolveSelectedTestInputs(configPath, selectors) {
2226
2243
  const [specs, fuzz] = await Promise.all([
@@ -2662,9 +2679,9 @@ async function listExecutionPlan(command, configPath, selectors, modes, listFlag
2662
2679
  return;
2663
2680
  const specFiles = command == "fuzz" ? [] : await resolveSelectedFiles(configPath, selectors);
2664
2681
  const fuzzFiles = command == "fuzz"
2665
- ? await resolveSelectedFuzzFiles(configPath, selectors)
2682
+ ? await resolveSelectedFuzzFiles(configPath, selectors, modes)
2666
2683
  : command == "test" && fuzzEnabled
2667
- ? await resolveSelectedFuzzFiles(configPath, selectors)
2684
+ ? await resolveSelectedFuzzFiles(configPath, selectors, modes)
2668
2685
  : [];
2669
2686
  const files = command == "fuzz" ? fuzzFiles : specFiles;
2670
2687
  if (!specFiles.length && !fuzzFiles.length) {
@@ -2710,8 +2727,7 @@ async function listExecutionPlan(command, configPath, selectors, modes, listFlag
2710
2727
  }
2711
2728
  }
2712
2729
  const envOverrides = {
2713
- ...config.env,
2714
- ...(modeName ? (config.modes[modeName]?.env ?? {}) : {}),
2730
+ ...active.env,
2715
2731
  ...(command == "build"
2716
2732
  ? active.buildOptions.env
2717
2733
  : command == "run" || command == "test"