as-test 1.2.0 → 1.3.0

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/bin/index.js CHANGED
@@ -3,8 +3,10 @@ import chalk from "chalk";
3
3
  import {
4
4
  build,
5
5
  BuildFailureError,
6
+ flushModeWarnings,
6
7
  formatInvocation as formatBuildInvocation,
7
8
  getBuildInvocationPreview,
9
+ warnOnUnknownModeReferences,
8
10
  } from "./commands/build.js";
9
11
  import { createRunReporter, run } from "./commands/run.js";
10
12
  import { executeBuildCommand } from "./commands/build.js";
@@ -26,6 +28,7 @@ import {
26
28
  resolveModeNames,
27
29
  resolveSpecRelativePath,
28
30
  } from "./util.js";
31
+ import { normalizeFeatureName } from "./types.js";
29
32
  import * as path from "path";
30
33
  import { spawnSync } from "child_process";
31
34
  import { glob } from "glob";
@@ -289,10 +292,10 @@ function printCommandHelp(command) {
289
292
  " --mode <name[,name...]> Run one or multiple named config modes\n",
290
293
  );
291
294
  process.stdout.write(
292
- " --enable <feature> Enable build feature (coverage|try-as)\n",
295
+ " --enable <list> Enable features, comma-separated (e.g. coverage,try-as,simd)\n",
293
296
  );
294
297
  process.stdout.write(
295
- " --disable <feature> Disable build feature (coverage|try-as)\n",
298
+ " --disable <list> Disable features, comma-separated\n",
296
299
  );
297
300
  process.stdout.write(
298
301
  " --parallel Run files through an ordered worker pool using an automatic worker count\n",
@@ -356,10 +359,10 @@ function printCommandHelp(command) {
356
359
  );
357
360
  process.stdout.write(" --suites <name[,name...]> Alias for --suite\n");
358
361
  process.stdout.write(
359
- " --enable <feature> Enable feature (coverage|try-as)\n",
362
+ " --enable <list> Enable features, comma-separated (e.g. coverage,try-as,simd)\n",
360
363
  );
361
364
  process.stdout.write(
362
- " --disable <feature> Disable feature (coverage|try-as)\n",
365
+ " --disable <list> Disable features, comma-separated\n",
363
366
  );
364
367
  process.stdout.write(
365
368
  " --reporter <name|path> Use built-in reporter (default|tap) or custom module path\n",
@@ -428,10 +431,10 @@ function printCommandHelp(command) {
428
431
  );
429
432
  process.stdout.write(" --suites <name[,name...]> Alias for --suite\n");
430
433
  process.stdout.write(
431
- " --enable <feature> Enable feature (coverage|try-as)\n",
434
+ " --enable <list> Enable features, comma-separated (e.g. coverage,try-as,simd)\n",
432
435
  );
433
436
  process.stdout.write(
434
- " --disable <feature> Disable feature (coverage|try-as)\n",
437
+ " --disable <list> Disable features, comma-separated\n",
435
438
  );
436
439
  process.stdout.write(
437
440
  " --fuzz Run fuzz targets after the normal test pass\n",
@@ -534,6 +537,12 @@ function printCommandHelp(command) {
534
537
  process.stdout.write(
535
538
  " --example <minimal|full|none> Set example template\n",
536
539
  );
540
+ process.stdout.write(
541
+ " --enable <list> Enable features, comma-separated (coverage,try-as)\n",
542
+ );
543
+ process.stdout.write(
544
+ " --disable <list> Disable features, comma-separated\n",
545
+ );
537
546
  process.stdout.write(
538
547
  " --install Install dependencies after scaffolding\n",
539
548
  );
@@ -709,8 +718,9 @@ function resolveCommandArgs(rawArgs, command) {
709
718
  return values;
710
719
  }
711
720
  function resolveFeatureToggles(rawArgs, command) {
712
- if (command !== "build" && command !== "run" && command !== "test") return {};
713
- const out = {};
721
+ if (command !== "build" && command !== "run" && command !== "test")
722
+ return { featureOverrides: {} };
723
+ const out = { featureOverrides: {} };
714
724
  let seenCommand = false;
715
725
  for (let i = 0; i < rawArgs.length; i++) {
716
726
  const arg = rawArgs[i];
@@ -722,7 +732,9 @@ function resolveFeatureToggles(rawArgs, command) {
722
732
  const enabled = arg == "--enable";
723
733
  const next = rawArgs[i + 1];
724
734
  if (next && !next.startsWith("-")) {
725
- applyFeatureToggle(out, next, enabled);
735
+ for (const name of splitFeatureList(next)) {
736
+ applyFeatureToggle(out, name, enabled);
737
+ }
726
738
  i++;
727
739
  }
728
740
  continue;
@@ -730,9 +742,9 @@ function resolveFeatureToggles(rawArgs, command) {
730
742
  if (arg.startsWith("--enable=") || arg.startsWith("--disable=")) {
731
743
  const enabled = arg.startsWith("--enable=");
732
744
  const eq = arg.indexOf("=");
733
- const value = arg.slice(eq + 1).trim();
734
- if (value.length) {
735
- applyFeatureToggle(out, value, enabled);
745
+ const value = arg.slice(eq + 1);
746
+ for (const name of splitFeatureList(value)) {
747
+ applyFeatureToggle(out, name, enabled);
736
748
  }
737
749
  }
738
750
  }
@@ -1280,19 +1292,24 @@ function parseIntegerFlag(flag, value) {
1280
1292
  }
1281
1293
  return Math.floor(parsed);
1282
1294
  }
1295
+ function splitFeatureList(value) {
1296
+ return value
1297
+ .split(",")
1298
+ .map((part) => part.trim())
1299
+ .filter((part) => part.length > 0);
1300
+ }
1283
1301
  function applyFeatureToggle(out, rawFeature, enabled) {
1284
- const key = rawFeature.trim().toLowerCase();
1302
+ const key = normalizeFeatureName(rawFeature);
1303
+ if (!key.length) {
1304
+ throw new Error(
1305
+ `empty feature name passed to ${enabled ? "--enable" : "--disable"}`,
1306
+ );
1307
+ }
1285
1308
  if (key == "coverage") {
1286
1309
  out.coverage = enabled;
1287
1310
  return;
1288
1311
  }
1289
- if (key == "try-as" || key == "try_as" || key == "tryas") {
1290
- out.tryAs = enabled;
1291
- return;
1292
- }
1293
- throw new Error(
1294
- `unknown feature "${rawFeature}". Supported features: coverage, try-as`,
1295
- );
1312
+ out.featureOverrides[key] = enabled;
1296
1313
  }
1297
1314
  function resolveCommandTokens(rawArgs, command) {
1298
1315
  const values = [];
@@ -1425,6 +1442,7 @@ async function runTestSequential(
1425
1442
  ),
1426
1443
  });
1427
1444
  reporter.flush?.();
1445
+ flushModeWarnings(process.argv.includes("--show-warnings"));
1428
1446
  }
1429
1447
  return {
1430
1448
  failed,
@@ -1874,6 +1892,7 @@ async function runTestModesCore(
1874
1892
  ),
1875
1893
  });
1876
1894
  reporterSession.reporter.flush?.();
1895
+ flushModeWarnings(process.argv.includes("--show-warnings"));
1877
1896
  }
1878
1897
  }
1879
1898
  } finally {
@@ -2063,6 +2082,14 @@ async function runTestMatrix(
2063
2082
  fuzzOverrides,
2064
2083
  ) {
2065
2084
  const files = await resolveSelectedFiles(configPath, selectors);
2085
+ if (files.length && configPath) {
2086
+ try {
2087
+ const loaded = loadConfig(configPath, false);
2088
+ warnOnUnknownModeReferences(files, loaded.modes ?? {});
2089
+ } catch {
2090
+ // Best-effort: never fail the run on a scan error.
2091
+ }
2092
+ }
2066
2093
  if (!files.length) {
2067
2094
  if (!fuzzEnabled) {
2068
2095
  throw await buildNoTestFilesMatchedError(configPath, selectors);
@@ -2215,6 +2242,7 @@ async function runTestMatrix(
2215
2242
  modeSummary: buildModeSummary(modeState, modeSummaryTotal),
2216
2243
  });
2217
2244
  reporter.flush?.();
2245
+ flushModeWarnings(process.argv.includes("--show-warnings"));
2218
2246
  return failed;
2219
2247
  }
2220
2248
  async function runFuzzModes(
@@ -2372,6 +2400,7 @@ async function runRuntimeSingleParallel(
2372
2400
  ),
2373
2401
  });
2374
2402
  reporter.flush?.();
2403
+ flushModeWarnings(process.argv.includes("--show-warnings"));
2375
2404
  return results.some((result) => result.failed);
2376
2405
  }
2377
2406
  async function runRuntimeMatrixParallel(
@@ -2516,6 +2545,7 @@ async function runRuntimeMatrixParallel(
2516
2545
  modeSummary: buildModeSummary(modeState, modeSummaryTotal),
2517
2546
  });
2518
2547
  reporter.flush?.();
2548
+ flushModeWarnings(process.argv.includes("--show-warnings"));
2519
2549
  return allResults.some((result) => result.failed);
2520
2550
  }
2521
2551
  async function runTestSingleParallel(
@@ -2671,6 +2701,7 @@ async function runTestSingleParallel(
2671
2701
  ),
2672
2702
  });
2673
2703
  reporter.flush?.();
2704
+ flushModeWarnings(process.argv.includes("--show-warnings"));
2674
2705
  return failed;
2675
2706
  }
2676
2707
  async function runTestMatrixParallel(
@@ -2687,6 +2718,14 @@ async function runTestMatrixParallel(
2687
2718
  fuzzOverrides,
2688
2719
  ) {
2689
2720
  const files = await resolveSelectedFiles(configPath, selectors);
2721
+ if (files.length && configPath) {
2722
+ try {
2723
+ const loaded = loadConfig(configPath, false);
2724
+ warnOnUnknownModeReferences(files, loaded.modes ?? {});
2725
+ } catch {
2726
+ // Best-effort: never fail the run on a scan error.
2727
+ }
2728
+ }
2690
2729
  if (!files.length) {
2691
2730
  if (!fuzzEnabled) {
2692
2731
  throw await buildNoTestFilesMatchedError(configPath, selectors);
@@ -2851,6 +2890,7 @@ async function runTestMatrixParallel(
2851
2890
  modeSummary: buildModeSummary(modeState, modeSummaryTotal),
2852
2891
  });
2853
2892
  reporter.flush?.();
2893
+ flushModeWarnings(process.argv.includes("--show-warnings"));
2854
2894
  return failed;
2855
2895
  }
2856
2896
  async function runFuzzMatrixResultsParallel(
package/bin/types.js CHANGED
@@ -8,6 +8,7 @@ export class Config {
8
8
  this.snapshotDir = "./.as-test/snapshots";
9
9
  this.config = "none";
10
10
  this.coverage = false;
11
+ this.features = [];
11
12
  this.env = {};
12
13
  this.buildOptions = new BuildOptions();
13
14
  this.runOptions = new RunOptions();
@@ -15,6 +16,12 @@ export class Config {
15
16
  this.modes = {};
16
17
  }
17
18
  }
19
+ export const INTERNAL_FEATURE_NAMES = new Set(["try-as"]);
20
+ export function normalizeFeatureName(value) {
21
+ const trimmed = value.trim().toLowerCase();
22
+ if (trimmed == "try_as" || trimmed == "tryas") return "try-as";
23
+ return trimmed;
24
+ }
18
25
  export class CoverageOptions {
19
26
  constructor() {
20
27
  this.enabled = false;
package/bin/util.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  CoverageIgnoreOptions,
7
7
  FuzzConfig,
8
8
  ModeConfig,
9
+ normalizeFeatureName,
9
10
  ReporterConfig,
10
11
  RunOptions,
11
12
  Runtime,
@@ -186,6 +187,7 @@ function parseConfigRaw(raw, configPath) {
186
187
  typeof config.fuzz.crashDir == "string" && config.fuzz.crashDir.length
187
188
  ? config.fuzz.crashDir
188
189
  : "./.as-test/crashes";
190
+ config.features = parseFeaturesField(raw.features);
189
191
  config.modes = parseModes(raw.modes, configDir);
190
192
  CONFIG_META.set(config, {
191
193
  sourcePath: configPath,
@@ -193,6 +195,19 @@ function parseConfigRaw(raw, configPath) {
193
195
  });
194
196
  return config;
195
197
  }
198
+ function parseFeaturesField(raw) {
199
+ if (!Array.isArray(raw)) return [];
200
+ const seen = new Set();
201
+ const out = [];
202
+ for (const value of raw) {
203
+ if (typeof value != "string") continue;
204
+ const name = normalizeFeatureName(value);
205
+ if (!name.length || seen.has(name)) continue;
206
+ seen.add(name);
207
+ out.push(name);
208
+ }
209
+ return out;
210
+ }
196
211
  const TOP_LEVEL_KEYS = new Set([
197
212
  "$schema",
198
213
  "input",
@@ -203,6 +218,7 @@ const TOP_LEVEL_KEYS = new Set([
203
218
  "snapshotDir",
204
219
  "config",
205
220
  "coverage",
221
+ "features",
206
222
  "env",
207
223
  "buildOptions",
208
224
  "fuzz",
@@ -238,6 +254,7 @@ function validateConfig(raw, configPath) {
238
254
  validateStringField(raw, "snapshotDir", "$", issues);
239
255
  validateStringField(raw, "config", "$", issues);
240
256
  validateCoverageField(raw, "coverage", "$", issues);
257
+ validateFeaturesField(raw, "features", "$", issues);
241
258
  validateEnvField(raw, "env", "$", issues);
242
259
  validateBuildOptionsField(raw, "buildOptions", "$", issues);
243
260
  validateFuzzField(raw, "fuzz", "$", issues);
@@ -742,12 +759,34 @@ function validateModesField(raw, key, pathPrefix, issues) {
742
759
  validateStringField(modeObj, "snapshotDir", modePath, issues);
743
760
  validateStringField(modeObj, "config", modePath, issues);
744
761
  validateCoverageField(modeObj, "coverage", modePath, issues);
762
+ validateFeaturesField(modeObj, "features", modePath, issues);
745
763
  validateFuzzField(modeObj, "fuzz", modePath, issues);
746
764
  validateEnvField(modeObj, "env", modePath, issues);
747
765
  validateBuildOptionsField(modeObj, "buildOptions", modePath, issues);
748
766
  validateRunOptionsField(modeObj, "runOptions", modePath, issues);
749
767
  }
750
768
  }
769
+ function validateFeaturesField(raw, key, pathPrefix, issues) {
770
+ if (!(key in raw) || raw[key] == undefined) return;
771
+ const value = raw[key];
772
+ if (!Array.isArray(value)) {
773
+ issues.push({
774
+ path: `${pathPrefix}.${key}`,
775
+ message: "must be an array of feature name strings",
776
+ fix: 'example: "features": ["try-as", "simd"]',
777
+ });
778
+ return;
779
+ }
780
+ for (let i = 0; i < value.length; i++) {
781
+ if (typeof value[i] != "string" || !value[i].trim().length) {
782
+ issues.push({
783
+ path: `${pathPrefix}.${key}[${i}]`,
784
+ message: "must be a non-empty string",
785
+ fix: 'feature names look like "try-as" or "simd"',
786
+ });
787
+ }
788
+ }
789
+ }
751
790
  function isStringArray(value) {
752
791
  return Array.isArray(value) && value.every((item) => typeof item == "string");
753
792
  }
@@ -1051,6 +1090,7 @@ function cloneConfig(config) {
1051
1090
  cloned.runOptions = cloneRunOptions(config.runOptions);
1052
1091
  cloned.fuzz = cloneFuzzConfig(config.fuzz);
1053
1092
  cloned.coverage = cloneCoverageOptions(config.coverage);
1093
+ cloned.features = [...config.features];
1054
1094
  cloned.modes = Object.fromEntries(
1055
1095
  Object.entries(config.modes).map(([name, mode]) => [
1056
1096
  name,
@@ -1211,6 +1251,9 @@ function mergeRootConfig(base, override) {
1211
1251
  raw.coverage,
1212
1252
  );
1213
1253
  }
1254
+ if (Array.isArray(raw.features)) {
1255
+ merged.features = [...override.features];
1256
+ }
1214
1257
  if ("env" in raw) {
1215
1258
  merged.env = { ...override.env };
1216
1259
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "as-test",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "author": "Jairus Tanaka",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,6 +25,7 @@
25
25
  "assemblyscript-prettier": "^3.0.4",
26
26
  "husky": "^9.1.7",
27
27
  "json-as": "^1.3.7",
28
+ "playwright": "^1.60.0",
28
29
  "prettier": "3.8.3",
29
30
  "try-as": "^1.1.0",
30
31
  "typescript": "^6.0.3",
@@ -91,7 +92,7 @@
91
92
  "typecheck": "tsc -p cli --noEmit && tsc -p tsconfig.lib.json --noEmit && tsc -p transform --noEmit",
92
93
  "lint": "eslint transform/src/**/*.ts tools/**/*.js eslint.config.js",
93
94
  "build:lib": "tsc -p ./tsconfig.lib.json",
94
- "build:transform": "tsc -p ./transform",
95
+ "build:transform": "tsc -p ./transform && prettier -w ./transform/",
95
96
  "build:cli": "tsc -p cli",
96
97
  "build:run": "npm run build:cli",
97
98
  "docs:dev": "vitepress dev docs",
@@ -102,7 +103,6 @@
102
103
  "prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run format",
103
104
  "commitmsg:verify": "bash ./scripts/commit-msg.sh",
104
105
  "precommit:verify": "bash ./scripts/pre-commit.sh",
105
- "prepush:verify": "bash ./scripts/pre-push.sh",
106
106
  "prepare": "husky"
107
107
  },
108
108
  "type": "module"
@@ -5,8 +5,10 @@ import { MockTransform } from "./mock.js";
5
5
  import { LocationTransform } from "./location.js";
6
6
  import { LogTransform } from "./log.js";
7
7
  import { isStdlib } from "./util.js";
8
+ import { NodeKind } from "./types.js";
8
9
  export default class Transformer extends Transform {
9
10
  afterParse(parser) {
11
+ patchModeName(parser, process.env.AS_TEST_MODE_NAME ?? "default");
10
12
  const mock = new MockTransform();
11
13
  const location = new LocationTransform();
12
14
  const log = new LogTransform(parser);
@@ -81,6 +83,30 @@ export default class Transformer extends Transform {
81
83
  }
82
84
  }
83
85
  }
86
+ function patchModeName(parser, modeName) {
87
+ for (const source of parser.sources) {
88
+ if (!source.normalizedPath.endsWith("assembly/src/mode.ts"))
89
+ continue;
90
+ for (const stmt of source.statements) {
91
+ if (stmt.kind !== NodeKind.Variable)
92
+ continue;
93
+ const decls = stmt.declarations;
94
+ for (const decl of decls) {
95
+ if (decl.name.text !== "AS_TEST_MODE_NAME")
96
+ continue;
97
+ if (!decl.initializer)
98
+ continue;
99
+ if (decl.initializer.kind !== NodeKind.Literal)
100
+ continue;
101
+ const literal = decl.initializer;
102
+ if (literal.literalKind !== 2)
103
+ continue;
104
+ literal.value = modeName;
105
+ return;
106
+ }
107
+ }
108
+ }
109
+ }
84
110
  function collectMockImportTargets(sources) {
85
111
  const out = new Set();
86
112
  const pattern = /\bmockImport\s*\(\s*["']([^"']+)["']/g;