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.
@@ -4,7 +4,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
4
  import * as path from "path";
5
5
  import { createInterface } from "readline";
6
6
  import { getCliVersion } from "../util.js";
7
- const TARGETS = ["wasi", "bindings"];
7
+ import { buildWebRunnerSource } from "./web-runner-source.js";
8
+ const TARGETS = ["wasi", "bindings", "web"];
8
9
  const EXAMPLE_MODES = ["minimal", "full", "none"];
9
10
  export async function init(rawArgs) {
10
11
  const options = parseInitArgs(rawArgs);
@@ -21,6 +22,7 @@ export async function init(rawArgs) {
21
22
  root: path.resolve(process.cwd(), options.dir),
22
23
  target: options.target ?? "wasi",
23
24
  example: options.example ?? "minimal",
25
+ fuzzExample: options.fuzzExample ?? false,
24
26
  installDependenciesNow: options.install ?? false,
25
27
  }
26
28
  : await runInteractiveOnboarding(options, rl);
@@ -28,7 +30,7 @@ export async function init(rawArgs) {
28
30
  console.log(chalk.bold.red("◆ Cancelled"));
29
31
  return;
30
32
  }
31
- printPlan(answers.root, answers.target, answers.example, answers.installDependenciesNow);
33
+ printPlan(answers.root, answers.target, answers.example, answers.fuzzExample, answers.installDependenciesNow);
32
34
  if (!options.yes) {
33
35
  const cont = await askYesNo("Continue with these changes?", rl, true);
34
36
  if (!cont) {
@@ -36,7 +38,7 @@ export async function init(rawArgs) {
36
38
  return;
37
39
  }
38
40
  }
39
- const summary = applyInit(answers.root, answers.target, answers.example, options.force);
41
+ const summary = applyInit(answers.root, answers.target, answers.example, answers.fuzzExample, options.force);
40
42
  printSummary(summary);
41
43
  console.log(chalk.bold.green("◆ Finished!"));
42
44
  if (answers.installDependenciesNow) {
@@ -73,6 +75,14 @@ function parseInitArgs(rawArgs) {
73
75
  options.install = true;
74
76
  continue;
75
77
  }
78
+ if (arg == "--fuzz-example") {
79
+ options.fuzzExample = true;
80
+ continue;
81
+ }
82
+ if (arg == "--no-fuzz-example") {
83
+ options.fuzzExample = false;
84
+ continue;
85
+ }
76
86
  if (arg == "--target") {
77
87
  const next = rawArgs[i + 1];
78
88
  if (next && !next.startsWith("-")) {
@@ -80,7 +90,7 @@ function parseInitArgs(rawArgs) {
80
90
  i++;
81
91
  continue;
82
92
  }
83
- throw new Error("--target requires a value: wasi|bindings");
93
+ throw new Error("--target requires a value: wasi|bindings|web");
84
94
  }
85
95
  if (arg.startsWith("--target=")) {
86
96
  options.target = parseTarget(arg.slice("--target=".length));
@@ -133,7 +143,7 @@ function parseInitArgs(rawArgs) {
133
143
  options.example = positional.shift();
134
144
  }
135
145
  if (positional.length > 0) {
136
- throw new Error(`Unknown init argument(s): ${positional.join(", ")}. Usage: init [dir] [--target wasi|bindings] [--example minimal|full|none] [--install] [--yes] [--force] [--dir <path>]`);
146
+ throw new Error(`Unknown init argument(s): ${positional.join(", ")}. Usage: init [dir] [--target wasi|bindings|web] [--example minimal|full|none] [--fuzz-example|--no-fuzz-example] [--install] [--yes] [--force] [--dir <path>]`);
137
147
  }
138
148
  return options;
139
149
  }
@@ -176,6 +186,10 @@ async function runInteractiveOnboarding(options, face) {
176
186
  value: "bindings",
177
187
  label: "bindings (default runner: node .as-test/runners/default.bindings.js)",
178
188
  },
189
+ {
190
+ value: "web",
191
+ label: "web (default runner: node .as-test/runners/default.web.js <file>)",
192
+ },
179
193
  ], face, "wasi"));
180
194
  if (options.target || onboardingMode == "quick") {
181
195
  printPromptAndSelectionLine("Build target", target);
@@ -191,6 +205,13 @@ async function runInteractiveOnboarding(options, face) {
191
205
  if (options.example || onboardingMode == "quick") {
192
206
  printPromptAndSelectionLine("Example template", example);
193
207
  }
208
+ const fuzzExample = options.fuzzExample ??
209
+ (onboardingMode == "quick"
210
+ ? false
211
+ : await askYesNo("Add a basic fuzzer example?", face, false));
212
+ if (options.fuzzExample !== undefined || onboardingMode == "quick") {
213
+ printPromptAndSelectionLine("Add a basic fuzzer example?", fuzzExample ? "Yes" : "No");
214
+ }
194
215
  const installDependenciesNow = options.install ??
195
216
  (onboardingMode == "quick"
196
217
  ? false
@@ -202,6 +223,7 @@ async function runInteractiveOnboarding(options, face) {
202
223
  root: resolvedRoot,
203
224
  target,
204
225
  example,
226
+ fuzzExample,
205
227
  installDependenciesNow,
206
228
  };
207
229
  }
@@ -213,9 +235,9 @@ function printOnboardingHeader() {
213
235
  // );
214
236
  }
215
237
  function printOnboardingIntro() {
216
- console.log(chalk.cyan("╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗"));
217
- console.log(chalk.cyan("╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║ "));
218
- console.log(chalk.cyan("╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ "));
238
+ console.log(chalk.bold.blue("╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗"));
239
+ console.log(chalk.bold.blue("╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║ "));
240
+ console.log(chalk.bold.blue("╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ "));
219
241
  console.log("");
220
242
  // console.log(chalk.bold("┌") + " " + chalk.bold.blueBright(""));
221
243
  // console.log("│");
@@ -284,7 +306,7 @@ function printSelectionLine(answer) {
284
306
  }
285
307
  function parseTarget(value) {
286
308
  if (!isTarget(value)) {
287
- throw new Error(`Invalid target "${value}". Expected wasi|bindings`);
309
+ throw new Error(`Invalid target "${value}". Expected wasi|bindings|web`);
288
310
  }
289
311
  return value;
290
312
  }
@@ -300,7 +322,7 @@ function isTarget(value) {
300
322
  function isExampleMode(value) {
301
323
  return EXAMPLE_MODES.includes(value);
302
324
  }
303
- function printPlan(root, target, example, install) {
325
+ function printPlan(root, target, example, fuzzExample, install) {
304
326
  const displayRoot = () => {
305
327
  const rel = path.relative(process.cwd(), root).split(path.sep).join("/");
306
328
  if (!rel || rel == ".")
@@ -363,11 +385,12 @@ function printPlan(root, target, example, install) {
363
385
  { path: ".as-test/coverage", isDir: true },
364
386
  { path: ".as-test/snapshots", isDir: true },
365
387
  { path: "assembly", isDir: true },
388
+ { path: "assembly/tsconfig.json", isDir: false },
366
389
  { path: "assembly/__tests__", isDir: true },
367
390
  { path: "as-test.config.json", isDir: false },
368
391
  { path: "package.json", isDir: false },
369
392
  ];
370
- if (target == "wasi" || target == "bindings") {
393
+ if (target == "wasi" || target == "bindings" || target == "web") {
371
394
  fileEntries.push({ path: ".as-test/runners", isDir: true });
372
395
  fileEntries.push({
373
396
  path: ".as-test/runners/default.bindings.js",
@@ -377,6 +400,10 @@ function printPlan(root, target, example, install) {
377
400
  path: ".as-test/runners/default.wasi.js",
378
401
  isDir: false,
379
402
  });
403
+ fileEntries.push({
404
+ path: ".as-test/runners/default.web.js",
405
+ isDir: false,
406
+ });
380
407
  }
381
408
  if (example != "none") {
382
409
  fileEntries.push({
@@ -384,10 +411,18 @@ function printPlan(root, target, example, install) {
384
411
  isDir: false,
385
412
  });
386
413
  }
414
+ if (fuzzExample) {
415
+ fileEntries.push({ path: "assembly/__fuzz__", isDir: true });
416
+ fileEntries.push({
417
+ path: "assembly/__fuzz__/example.fuzz.ts",
418
+ isDir: false,
419
+ });
420
+ }
387
421
  const treeRoot = buildTree(fileEntries);
388
422
  console.log(chalk.bold.blue("◇ Planned Changes"));
389
423
  console.log("│" + chalk.dim(` - Target: ${target}`));
390
424
  console.log("│" + chalk.dim(` - Example: ${example}`));
425
+ console.log("│" + chalk.dim(` - Fuzzer example: ${fuzzExample ? "yes" : "no"}`));
391
426
  console.log("│" + chalk.dim(` - Directory: ${displayRoot()}`));
392
427
  console.log("│" + chalk.dim(` - Install dependencies: ${install ? "yes" : "no"}`));
393
428
  console.log("│" + chalk.bold.blue(" File Changes"));
@@ -397,7 +432,7 @@ function printPlan(root, target, example, install) {
397
432
  }
398
433
  console.log("│");
399
434
  }
400
- function applyInit(root, target, example, force) {
435
+ function applyInit(root, target, example, fuzzExample, force) {
401
436
  const summary = {
402
437
  created: [],
403
438
  updated: [],
@@ -408,18 +443,34 @@ function applyInit(root, target, example, force) {
408
443
  ensureDir(root, ".as-test/coverage", summary);
409
444
  ensureDir(root, ".as-test/snapshots", summary);
410
445
  ensureDir(root, "assembly/__tests__", summary);
411
- if (target == "wasi" || target == "bindings") {
446
+ if (fuzzExample) {
447
+ ensureDir(root, "assembly/__fuzz__", summary);
448
+ }
449
+ if (target == "wasi" || target == "bindings" || target == "web") {
412
450
  ensureDir(root, ".as-test/runners", summary);
413
451
  }
414
452
  ensureGitignoreIncludesAsTestDirs(root, summary);
453
+ writeJson(path.join(root, "assembly/tsconfig.json"), buildAssemblyTsconfig(), summary, "assembly/tsconfig.json");
415
454
  const configPath = path.join(root, "as-test.config.json");
416
455
  const config = {
417
456
  $schema: "node_modules/as-test/as-test.config.schema.json",
418
457
  input: ["assembly/__tests__/*.spec.ts"],
419
458
  output: ".as-test/",
420
459
  config: "none",
421
- coverage: true,
460
+ coverage: false,
422
461
  env: {},
462
+ ...(fuzzExample
463
+ ? {
464
+ fuzz: {
465
+ input: ["assembly/__fuzz__/*.fuzz.ts"],
466
+ runs: 1000,
467
+ seed: 1337,
468
+ target: "bindings",
469
+ corpusDir: ".as-test/corpus",
470
+ crashDir: ".as-test/crashes",
471
+ },
472
+ }
473
+ : {}),
423
474
  buildOptions: {
424
475
  target,
425
476
  },
@@ -427,11 +478,23 @@ function applyInit(root, target, example, force) {
427
478
  runtime: {
428
479
  cmd: target == "wasi"
429
480
  ? "node .as-test/runners/default.wasi.js <file>"
430
- : "node .as-test/runners/default.bindings.js <file>",
481
+ : target == "bindings"
482
+ ? "node .as-test/runners/default.bindings.js <file>"
483
+ : "node .as-test/runners/default.web.js <file>",
431
484
  },
432
485
  reporter: "default",
433
486
  },
434
- modes: {},
487
+ modes: target == "web"
488
+ ? {
489
+ "web-headless": {
490
+ runOptions: {
491
+ runtime: {
492
+ cmd: "node .as-test/runners/default.web.js --headless <file>",
493
+ },
494
+ },
495
+ },
496
+ }
497
+ : {},
435
498
  };
436
499
  writeJson(configPath, config, summary, "as-test.config.json");
437
500
  if (example != "none") {
@@ -439,14 +502,22 @@ function applyInit(root, target, example, force) {
439
502
  const content = example == "minimal" ? buildMinimalExampleSpec() : buildFullExampleSpec();
440
503
  writeManagedFile(examplePath, content, force, summary, "assembly/__tests__/example.spec.ts");
441
504
  }
442
- if (target == "wasi" || target == "bindings") {
505
+ if (fuzzExample) {
506
+ const fuzzPath = path.join(root, "assembly/__fuzz__/example.fuzz.ts");
507
+ writeManagedFile(fuzzPath, buildBasicFuzzerExample(), force, summary, "assembly/__fuzz__/example.fuzz.ts");
508
+ }
509
+ if (target == "wasi" || target == "bindings" || target == "web") {
443
510
  const runnerPath = path.join(root, ".as-test/runners/default.wasi.js");
444
511
  writeManagedFile(runnerPath, buildWasiRunner(), force, summary, ".as-test/runners/default.wasi.js");
445
512
  }
446
- if (target == "wasi" || target == "bindings") {
513
+ if (target == "wasi" || target == "bindings" || target == "web") {
447
514
  const runnerPath = path.join(root, ".as-test/runners/default.bindings.js");
448
515
  writeManagedFile(runnerPath, buildBindingsRunner(), force, summary, ".as-test/runners/default.bindings.js");
449
516
  }
517
+ if (target == "wasi" || target == "bindings" || target == "web") {
518
+ const runnerPath = path.join(root, ".as-test/runners/default.web.js");
519
+ writeManagedFile(runnerPath, buildWebRunnerSource(), force, summary, ".as-test/runners/default.web.js");
520
+ }
450
521
  const pkgPath = path.join(root, "package.json");
451
522
  const pkg = existsSync(pkgPath)
452
523
  ? JSON.parse(readFileSync(pkgPath, "utf8"))
@@ -458,6 +529,9 @@ function applyInit(root, target, example, force) {
458
529
  if (!scripts.test) {
459
530
  scripts.test = "ast test";
460
531
  }
532
+ if (fuzzExample && !scripts.fuzz) {
533
+ scripts.fuzz = "ast fuzz";
534
+ }
461
535
  if (!pkg.type) {
462
536
  pkg.type = "module";
463
537
  }
@@ -501,7 +575,13 @@ function ensureDir(root, rel, summary) {
501
575
  function ensureGitignoreIncludesAsTestDirs(root, summary) {
502
576
  const rel = ".gitignore";
503
577
  const fullPath = path.join(root, rel);
504
- const entries = ["!.as-test/runners/", "!.as-test/snapshots/"];
578
+ const entries = [
579
+ "# Include essential as-test artifacts",
580
+ "!.as-test/",
581
+ ".as-test/*",
582
+ "!.as-test/runners/",
583
+ "!.as-test/snapshots/",
584
+ ];
505
585
  const existed = existsSync(fullPath);
506
586
  const source = existed ? readFileSync(fullPath, "utf8") : "";
507
587
  const lines = source.split(/\r?\n/);
@@ -521,6 +601,12 @@ function ensureGitignoreIncludesAsTestDirs(root, summary) {
521
601
  else
522
602
  summary.created.push(rel);
523
603
  }
604
+ function buildAssemblyTsconfig() {
605
+ return {
606
+ extends: "assemblyscript/std/assembly.json",
607
+ include: ["./**/*.ts"],
608
+ };
609
+ }
524
610
  function writeJson(fullPath, value, summary, displayPath) {
525
611
  const rel = displayPath ??
526
612
  path.relative(process.cwd(), fullPath) ??
@@ -827,19 +913,17 @@ function resolveInstallCommand(root) {
827
913
  return { command: "npm", args: ["install"] };
828
914
  }
829
915
  function buildMinimalExampleSpec() {
830
- return `import { describe, expect, test, run } from "as-test";
916
+ return `import { describe, expect, test } from "as-test";
831
917
 
832
918
  describe("example", () => {
833
919
  test("adds numbers", () => {
834
920
  expect(1 + 2).toBe(3);
835
921
  });
836
922
  });
837
-
838
- run();
839
923
  `;
840
924
  }
841
925
  function buildFullExampleSpec() {
842
- return `import { afterAll, beforeAll, describe, expect, it, log, run, test } from "as-test";
926
+ return `import { afterAll, beforeAll, describe, expect, it, log, test } from "as-test";
843
927
 
844
928
  beforeAll(() => {
845
929
  log("setup");
@@ -869,8 +953,24 @@ describe("strings", () => {
869
953
  expect("as-test").toStartWith("as");
870
954
  });
871
955
  });
956
+ `;
957
+ }
958
+ function buildBasicFuzzerExample() {
959
+ return `import { expect, fuzz, FuzzSeed } from "as-test";
872
960
 
873
- run();
961
+ fuzz("basic string fuzzer", (value: string): bool => {
962
+ expect(value.length >= 0).toBe(true);
963
+ return value.length <= 24;
964
+ }).generate((seed: FuzzSeed, run: (value: string) => bool): void => {
965
+ run(
966
+ seed.string({
967
+ charset: "ascii",
968
+ min: 0,
969
+ max: 24,
970
+ exclude: [0x00, 0x0a, 0x0d],
971
+ }),
972
+ );
973
+ });
874
974
  `;
875
975
  }
876
976
  function buildWasiRunner() {
@@ -909,6 +1009,11 @@ try {
909
1009
  const binary = readFileSync(wasmPath);
910
1010
  const module = new WebAssembly.Module(binary);
911
1011
  const instance = new WebAssembly.Instance(module, {
1012
+ env: {
1013
+ __as_test_request_fuzz_config() {
1014
+ return 0;
1015
+ },
1016
+ },
912
1017
  wasi_snapshot_preview1: wasi.wasiImport,
913
1018
  });
914
1019
  wasi.start(instance);