as-test 1.1.1 → 1.1.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.
@@ -79,14 +79,24 @@ export class FuzzSeed {
79
79
 
80
80
  i8(options: IntegerOptions<i8> | null = null): i8 {
81
81
  if (options == null) {
82
- return this.nextI8InRange(i8.MIN_VALUE, i8.MAX_VALUE, EMPTY_I8_EXCLUDE, false);
82
+ return this.nextI8InRange(
83
+ i8.MIN_VALUE,
84
+ i8.MAX_VALUE,
85
+ EMPTY_I8_EXCLUDE,
86
+ false,
87
+ );
83
88
  }
84
89
  return this.nextI8InRange(options.min, options.max, options.exclude, true);
85
90
  }
86
91
 
87
92
  u8(options: IntegerOptions<u8> | null = null): u8 {
88
93
  if (options == null) {
89
- return this.nextU8InRange(u8.MIN_VALUE, u8.MAX_VALUE, EMPTY_U8_EXCLUDE, false);
94
+ return this.nextU8InRange(
95
+ u8.MIN_VALUE,
96
+ u8.MAX_VALUE,
97
+ EMPTY_U8_EXCLUDE,
98
+ false,
99
+ );
90
100
  }
91
101
  return this.nextU8InRange(options.min, options.max, options.exclude, true);
92
102
  }
@@ -186,7 +196,9 @@ export class FuzzSeed {
186
196
  if (!exclude.length) {
187
197
  for (let i = 0; i < length; i++) {
188
198
  unchecked(
189
- (out[i] = <u8>unchecked(include[this.nextRange(0, include.length - 1)])),
199
+ (out[i] = <u8>(
200
+ unchecked(include[this.nextRange(0, include.length - 1)])
201
+ )),
190
202
  );
191
203
  }
192
204
  return out;
@@ -299,7 +311,9 @@ export class FuzzSeed {
299
311
  return <i32>this.nextU32();
300
312
  }
301
313
  if (!exclude.length) {
302
- return max <= min ? min : min + <i32>(this.nextU32() % <u32>(max - min + 1));
314
+ return max <= min
315
+ ? min
316
+ : min + <i32>(this.nextU32() % <u32>(max - min + 1));
303
317
  }
304
318
  for (let attempts = 0; attempts < 1024; attempts++) {
305
319
  const value =
@@ -324,12 +338,16 @@ export class FuzzSeed {
324
338
  const right = <i32>max;
325
339
  if (!exclude.length) {
326
340
  return <i8>(
327
- right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
341
+ (right <= left
342
+ ? left
343
+ : left + <i32>(this.nextU32() % <u32>(right - left + 1)))
328
344
  );
329
345
  }
330
346
  for (let attempts = 0; attempts < 1024; attempts++) {
331
347
  const value = <i8>(
332
- right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
348
+ (right <= left
349
+ ? left
350
+ : left + <i32>(this.nextU32() % <u32>(right - left + 1)))
333
351
  );
334
352
  if (!containsValue<i8>(exclude, value)) return value;
335
353
  }
@@ -350,11 +368,13 @@ export class FuzzSeed {
350
368
  const left = <u32>min;
351
369
  const right = <u32>max;
352
370
  if (!exclude.length) {
353
- return <u8>(right <= left ? left : left + (this.nextU32() % (right - left + 1)));
371
+ return <u8>(
372
+ (right <= left ? left : left + (this.nextU32() % (right - left + 1)))
373
+ );
354
374
  }
355
375
  for (let attempts = 0; attempts < 1024; attempts++) {
356
376
  const value = <u8>(
357
- right <= left ? left : left + (this.nextU32() % (right - left + 1))
377
+ (right <= left ? left : left + (this.nextU32() % (right - left + 1)))
358
378
  );
359
379
  if (!containsValue<u8>(exclude, value)) return value;
360
380
  }
@@ -376,12 +396,16 @@ export class FuzzSeed {
376
396
  const right = <i32>max;
377
397
  if (!exclude.length) {
378
398
  return <i16>(
379
- right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
399
+ (right <= left
400
+ ? left
401
+ : left + <i32>(this.nextU32() % <u32>(right - left + 1)))
380
402
  );
381
403
  }
382
404
  for (let attempts = 0; attempts < 1024; attempts++) {
383
405
  const value = <i16>(
384
- right <= left ? left : left + <i32>(this.nextU32() % <u32>(right - left + 1))
406
+ (right <= left
407
+ ? left
408
+ : left + <i32>(this.nextU32() % <u32>(right - left + 1)))
385
409
  );
386
410
  if (!containsValue<i16>(exclude, value)) return value;
387
411
  }
@@ -402,11 +426,13 @@ export class FuzzSeed {
402
426
  const left = <u32>min;
403
427
  const right = <u32>max;
404
428
  if (!exclude.length) {
405
- return <u16>(right <= left ? left : left + (this.nextU32() % (right - left + 1)));
429
+ return <u16>(
430
+ (right <= left ? left : left + (this.nextU32() % (right - left + 1)))
431
+ );
406
432
  }
407
433
  for (let attempts = 0; attempts < 1024; attempts++) {
408
434
  const value = <u16>(
409
- right <= left ? left : left + (this.nextU32() % (right - left + 1))
435
+ (right <= left ? left : left + (this.nextU32() % (right - left + 1)))
410
436
  );
411
437
  if (!containsValue<u16>(exclude, value)) return value;
412
438
  }
@@ -448,9 +474,7 @@ export class FuzzSeed {
448
474
  const left = this.toOrderedU64(min);
449
475
  const right = this.toOrderedU64(max);
450
476
  if (!exclude.length) {
451
- return this.fromOrderedU64(
452
- left + this.nextU64Offset(left, right),
453
- );
477
+ return this.fromOrderedU64(left + this.nextU64Offset(left, right));
454
478
  }
455
479
  for (let attempts = 0; attempts < 1024; attempts++) {
456
480
  const value = this.fromOrderedU64(left + this.nextU64Offset(left, right));
@@ -529,7 +553,7 @@ export class FuzzSeed {
529
553
  }
530
554
 
531
555
  private toOrderedU64(value: i64): u64 {
532
- return <u64>value ^ I64_SIGN_MASK;
556
+ return (<u64>value) ^ I64_SIGN_MASK;
533
557
  }
534
558
 
535
559
  private fromOrderedU64(value: u64): i64 {
@@ -543,13 +567,11 @@ const DIGIT_ALPHABET: i32[] = rangeChars(48, 57);
543
567
  const HEX_ALPHABET: i32[] = DIGIT_ALPHABET.concat(rangeChars(97, 102));
544
568
  const ALNUM_ALPHABET: i32[] = ALPHA_ALPHABET.concat(DIGIT_ALPHABET);
545
569
  const BASE64_ALPHABET: i32[] = ALPHA_ALPHABET.concat(DIGIT_ALPHABET).concat([
546
- 43,
547
- 47,
548
- 61,
570
+ 43, 47, 61,
549
571
  ]);
550
- const IDENTIFIER_ALPHABET: i32[] = [95].concat(ALPHA_ALPHABET).concat(
551
- DIGIT_ALPHABET,
552
- );
572
+ const IDENTIFIER_ALPHABET: i32[] = [95]
573
+ .concat(ALPHA_ALPHABET)
574
+ .concat(DIGIT_ALPHABET);
553
575
  const WHITESPACE_ALPHABET: i32[] = [9, 10, 13, 32];
554
576
 
555
577
  export abstract class FuzzerBase {
@@ -1113,7 +1135,6 @@ function removeFirst(values: i32[], needle: i32): void {
1113
1135
  if (index >= 0) values.splice(index, 1);
1114
1136
  }
1115
1137
 
1116
-
1117
1138
  function validateLengthRange(label: string, min: i32, max: i32): void {
1118
1139
  if (min < 0 || max < 0) panic();
1119
1140
  if (max < min) panic();
@@ -131,6 +131,8 @@ export class Suite {
131
131
  this.verdict = "ok";
132
132
  } else if (hasSkip) {
133
133
  this.verdict = "skip";
134
+ } else if (isTestCase) {
135
+ this.verdict = "ok";
134
136
  } else {
135
137
  this.verdict = "none";
136
138
  }
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from "as-test";
2
+ // Import JSON and bs directly from json-as.
3
+ // bs import prevents json-as transform from adding broken pnpm paths.
4
+ import { JSON } from "json-as/assembly";
5
+
6
+
7
+ @json
8
+ class SimpleData {
9
+ name: string = "";
10
+ count: i32 = 0;
11
+ }
12
+
13
+
14
+ @json
15
+ class NestedData {
16
+ id: string = "";
17
+ items: string[] = [];
18
+ }
19
+
20
+ describe("JSON", () => {
21
+ describe("stringify", () => {
22
+ it("should serialize a simple class", () => {
23
+ const data = new SimpleData();
24
+ data.name = "test";
25
+ data.count = 42;
26
+
27
+ const json = JSON.stringify(data);
28
+
29
+ expect(json).toBe('{"name":"test","count":42}');
30
+ });
31
+
32
+ it("should serialize a class with arrays", () => {
33
+ const data = new NestedData();
34
+ data.id = "abc123";
35
+ data.items = ["item1", "item2"];
36
+
37
+ const json = JSON.stringify(data);
38
+
39
+ expect(json).toBe('{"id":"abc123","items":["item1","item2"]}');
40
+ });
41
+
42
+ it("should serialize primitive types", () => {
43
+ expect(JSON.stringify<i32>(42)).toBe("42");
44
+ expect(JSON.stringify<bool>(true)).toBe("true");
45
+ expect(JSON.stringify<string>("hello")).toBe('"hello"');
46
+ });
47
+
48
+ it("should serialize arrays", () => {
49
+ const arr: i32[] = [1, 2, 3];
50
+ expect(JSON.stringify(arr)).toBe("[1,2,3]");
51
+ });
52
+ });
53
+
54
+ describe("parse", () => {
55
+ it("should deserialize a simple class", () => {
56
+ const json = '{"name":"test","count":42}';
57
+ const data = JSON.parse<SimpleData>(json);
58
+
59
+ expect(data.name).toBe("test");
60
+ expect(data.count).toBe(42);
61
+ });
62
+
63
+ it("should deserialize a class with arrays", () => {
64
+ const json = '{"id":"abc123","items":["item1","item2"]}';
65
+ const data = JSON.parse<NestedData>(json);
66
+
67
+ expect(data.id).toBe("abc123");
68
+ expect(data.items.length).toBe(2);
69
+ expect(data.items[0]).toBe("item1");
70
+ expect(data.items[1]).toBe("item2");
71
+ });
72
+
73
+ it("should round-trip data correctly", () => {
74
+ const original = new SimpleData();
75
+ original.name = "round-trip";
76
+ original.count = 123;
77
+
78
+ const json = JSON.stringify(original);
79
+ const restored = JSON.parse<SimpleData>(json);
80
+
81
+ expect(restored.name).toBe(original.name);
82
+ expect(restored.count).toBe(original.count);
83
+ });
84
+ });
85
+ });
@@ -35,7 +35,10 @@ export function formatValue<T>(value: T, deep: boolean = false): string {
35
35
  let out = "Map(" + keys.length.toString() + ") { ";
36
36
  for (let i = 0; i < keys.length; i++) {
37
37
  if (i) out += ", ";
38
- out += formatValue(changetype<valueof<typeof keys>>(unchecked(keys[i])), true);
38
+ out += formatValue(
39
+ changetype<valueof<typeof keys>>(unchecked(keys[i])),
40
+ true,
41
+ );
39
42
  out += " => ";
40
43
  out += formatValue(
41
44
  changetype<valueof<typeof values>>(unchecked(values[i])),
@@ -73,31 +76,37 @@ export function formatValue<T>(value: T, deep: boolean = false): string {
73
76
  return nameof<T>();
74
77
  }
75
78
 
79
+
76
80
  @inline
77
81
  export function colorText(format: i32[], text: string): string {
78
82
  return `\u001b[${format[0].toString()}m${text}\u001b[${format[1].toString()}m`;
79
83
  }
80
84
 
85
+
81
86
  @inline
82
87
  export function red(text: string): string {
83
88
  return colorText([31, 39], text);
84
89
  }
85
90
 
91
+
86
92
  @inline
87
93
  export function green(text: string): string {
88
94
  return colorText([32, 39], text);
89
95
  }
90
96
 
97
+
91
98
  @inline
92
99
  export function bgRed(text: string): string {
93
100
  return colorText([41, 49], text);
94
101
  }
95
102
 
103
+
96
104
  @inline
97
105
  export function bgGreen(text: string): string {
98
106
  return colorText([42, 49], text);
99
107
  }
100
108
 
109
+
101
110
  @inline
102
111
  export function bold(text: string): string {
103
112
  return colorText([1, 22], text);
@@ -154,8 +154,7 @@ export function requestFuzzConfig(): FuzzConfigReply {
154
154
  const runs = body.slice(0, first);
155
155
  const seed =
156
156
  second >= 0 ? body.slice(first + 1, second) : body.slice(first + 1);
157
- const kind =
158
- second >= 0 && third >= 0 ? body.slice(second + 1, third) : "";
157
+ const kind = second >= 0 && third >= 0 ? body.slice(second + 1, third) : "";
159
158
  const value = third >= 0 ? body.slice(third + 1) : "";
160
159
  if (runs.length) reply.runs = I32.parseInt(runs);
161
160
  if (seed.length) reply.seed = U64.parseInt(seed);
@@ -21,6 +21,7 @@ export class BuildWorkerPool {
21
21
  configPath: args.configPath,
22
22
  file: args.file,
23
23
  modeName: args.modeName,
24
+ buildCommand: args.buildCommand,
24
25
  featureToggles,
25
26
  overrides,
26
27
  resolve,
@@ -80,7 +81,12 @@ export class BuildWorkerPool {
80
81
  worker.busy = false;
81
82
  worker.task = null;
82
83
  if (failedTask) {
83
- failedTask.reject(new Error("build worker exited unexpectedly"));
84
+ const modeLabel = failedTask.modeName ?? "default";
85
+ const fileLabel = failedTask.file;
86
+ const commandText = failedTask.buildCommand?.trim().length
87
+ ? `\nBuild command: ${failedTask.buildCommand}`
88
+ : "";
89
+ failedTask.reject(new Error(`build worker exited unexpectedly while building ${fileLabel} in mode ${modeLabel}${commandText}`));
84
90
  }
85
91
  if (!pool || this.closed)
86
92
  return;
@@ -8,7 +8,7 @@ import { applyMode, getPkgRunner, loadConfig, tokenizeCommand, resolveProjectMod
8
8
  import { persistCrashRecord } from "../crash-store.js";
9
9
  import { BuildWorkerPool } from "../build-worker-pool.js";
10
10
  const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
11
- class BuildFailureError extends Error {
11
+ export class BuildFailureError extends Error {
12
12
  constructor(args) {
13
13
  super(args.message);
14
14
  this.name = "BuildFailureError";
@@ -18,6 +18,7 @@ class BuildFailureError extends Error {
18
18
  this.stdout = args.stdout;
19
19
  this.stderr = args.stderr;
20
20
  this.kind = args.kind;
21
+ this.crashLogPath = args.crashLogPath;
21
22
  }
22
23
  }
23
24
  export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}, overrides = {}, resolvedConfig) {
@@ -49,10 +50,13 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
49
50
  !hasCustomBuildCommand(config)) {
50
51
  const pool = getSerialBuildWorkerPool();
51
52
  for (const file of inputFiles) {
53
+ const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName, duplicateSpecBasenames)}`;
54
+ const invocation = getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles);
52
55
  await pool.buildFileMode({
53
56
  configPath,
54
57
  file,
55
58
  modeName,
59
+ buildCommand: formatInvocation(invocation),
56
60
  featureToggles,
57
61
  overrides,
58
62
  });
@@ -90,6 +94,7 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
90
94
  stdout,
91
95
  stderr,
92
96
  kind,
97
+ crashLogPath: crash.logPath,
93
98
  message: `Failed to build ${path.basename(file)} in mode ${modeLabel} with ${stderr || stdout || "unknown build error"}\n` +
94
99
  `Build command: ${buildCommand}\n` +
95
100
  `Crash log: ${crash.logPath}`,
@@ -1,6 +1,6 @@
1
1
  import { closeSerialBuildWorkerPool, } from "./build-core.js";
2
2
  export { build } from "./build-core.js";
3
- export { formatInvocation, getBuildInvocationPreview, getBuildReuseInfo, } from "./build-core.js";
3
+ export { BuildFailureError, formatInvocation, getBuildInvocationPreview, getBuildReuseInfo, } from "./build-core.js";
4
4
  export async function executeBuildCommand(rawArgs, configPath, selectedModes, deps) {
5
5
  const commandArgs = deps.resolveCommandArgs(rawArgs, "build");
6
6
  const listFlags = deps.resolveListFlags(rawArgs, "build");
@@ -112,7 +112,9 @@ function pruneNestedTargets(targets) {
112
112
  if (targetPath == otherPath)
113
113
  continue;
114
114
  const relative = path.relative(targetPath, otherPath);
115
- if (!relative.length || relative == ".." || relative.startsWith(`..${path.sep}`)) {
115
+ if (!relative.length ||
116
+ relative == ".." ||
117
+ relative.startsWith(`..${path.sep}`)) {
116
118
  continue;
117
119
  }
118
120
  targets.delete(otherPath);
@@ -1,16 +1,10 @@
1
- import chalk from "chalk";
2
- import { createInterface } from "readline";
3
1
  import { clean } from "./clean-core.js";
4
2
  import { loadConfig } from "../util.js";
5
3
  export { clean } from "./clean-core.js";
6
4
  export async function executeCleanCommand(rawArgs, configPath, selectedModes, resolveExecutionModes) {
7
- const force = rawArgs.includes("-f") || rawArgs.includes("--force");
8
5
  const modeTargets = selectedModes.length > 0
9
6
  ? resolveExecutionModes(configPath, selectedModes)
10
7
  : resolveAllCleanModes(configPath);
11
- if (!force && selectedModes.length == 0) {
12
- await confirmFullClean(configPath);
13
- }
14
8
  await clean(configPath, modeTargets, selectedModes.length == 0);
15
9
  }
16
10
  function resolveAllCleanModes(configPath) {
@@ -18,34 +12,3 @@ function resolveAllCleanModes(configPath) {
18
12
  const config = loadConfig(resolvedConfigPath, true);
19
13
  return [undefined, ...Object.keys(config.modes)];
20
14
  }
21
- async function confirmFullClean(configPath) {
22
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
23
- throw new Error('clean without --mode requires confirmation. Re-run with "-f" or "--force" to skip the prompt.');
24
- }
25
- const target = configPath ? ` in ${configPath}` : "";
26
- process.stdout.write(chalk.bold.blue("◇ Confirm Clean") +
27
- "\n" +
28
- `│ This will remove configured build outputs, crash reports, and logs for every mode${target}.\n` +
29
- "│\n");
30
- const answer = await promptLine("Continue? [Y/n] ");
31
- const normalized = answer.trim().toLowerCase();
32
- if (normalized == "" || normalized == "y" || normalized == "yes")
33
- return;
34
- if (normalized == "n" || normalized == "no") {
35
- process.stdout.write(chalk.dim("clean cancelled\n"));
36
- process.exit(0);
37
- }
38
- throw new Error(`invalid answer "${answer}". Expected yes or no.`);
39
- }
40
- function promptLine(question) {
41
- return new Promise((resolve) => {
42
- const rl = createInterface({
43
- input: process.stdin,
44
- output: process.stdout,
45
- });
46
- rl.question(question, (answer) => {
47
- rl.close();
48
- resolve(answer);
49
- });
50
- });
51
- }
@@ -292,10 +292,10 @@ function buildFuzzCrashEntryKey(file, modeName) {
292
292
  return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}`;
293
293
  }
294
294
  function sanitizeEntryName(name) {
295
- return name
295
+ return (name
296
296
  .toLowerCase()
297
297
  .replace(/[^a-z0-9]+/g, "-")
298
- .replace(/^-+|-+$/g, "") || "fuzzer";
298
+ .replace(/^-+|-+$/g, "") || "fuzzer");
299
299
  }
300
300
  function captureFrames(onFrame) {
301
301
  const originalWrite = process.stdout.write.bind(process.stdout);