as-test 0.5.3 → 1.0.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.
@@ -0,0 +1,6 @@
1
+ import { init } from "./init-core.js";
2
+ export { init } from "./init-core.js";
3
+ export async function executeInitCommand(rawArgs, deps) {
4
+ const commandTokens = deps.resolveCommandTokens(rawArgs, "init");
5
+ await init(commandTokens);
6
+ }
@@ -1,12 +1,12 @@
1
1
  import chalk from "chalk";
2
2
  import { spawn } from "child_process";
3
3
  import { glob } from "glob";
4
- import { applyMode, getExec, loadConfig } from "./util.js";
4
+ import { applyMode, getExec, loadConfig, tokenizeCommand } from "../util.js";
5
5
  import * as path from "path";
6
6
  import { pathToFileURL } from "url";
7
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
- import { createReporter as createDefaultReporter } from "./reporters/default.js";
9
- import { createTapReporter } from "./reporters/tap.js";
8
+ import { createReporter as createDefaultReporter } from "../reporters/default.js";
9
+ import { createTapReporter } from "../reporters/tap.js";
10
10
  const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
11
11
  var MessageType;
12
12
  (function (MessageType) {
@@ -87,7 +87,7 @@ class Channel {
87
87
  Channel.MAGIC = Buffer.from("WIPC");
88
88
  Channel.HEADER_SIZE = 9;
89
89
  class SnapshotStore {
90
- constructor(specFile, snapshotDir) {
90
+ constructor(specFile, snapshotDir, duplicateSpecBasenames = new Set()) {
91
91
  this.dirty = false;
92
92
  this.created = 0;
93
93
  this.updated = 0;
@@ -95,12 +95,22 @@ class SnapshotStore {
95
95
  this.failed = 0;
96
96
  this.warnedMissing = new Set();
97
97
  const base = path.basename(specFile, ".ts");
98
+ const disambiguator = resolveDisambiguator(specFile, duplicateSpecBasenames);
99
+ const snapshotBase = disambiguator.length
100
+ ? `${base}.${disambiguator}`
101
+ : base;
98
102
  const dir = path.join(process.cwd(), snapshotDir);
99
103
  if (!existsSync(dir))
100
104
  mkdirSync(dir, { recursive: true });
101
- this.filePath = path.join(dir, `${base}.snap.json`);
102
- this.data = existsSync(this.filePath)
103
- ? JSON.parse(readFileSync(this.filePath, "utf8"))
105
+ this.filePath = path.join(dir, `${snapshotBase}.snap.json`);
106
+ const legacyFilePath = path.join(dir, `${base}.snap.json`);
107
+ const sourcePath = existsSync(this.filePath)
108
+ ? this.filePath
109
+ : existsSync(legacyFilePath)
110
+ ? legacyFilePath
111
+ : null;
112
+ this.data = sourcePath
113
+ ? JSON.parse(readFileSync(sourcePath, "utf8"))
104
114
  : {};
105
115
  }
106
116
  assert(key, actual, allowSnapshot, updateSnapshots) {
@@ -151,6 +161,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
151
161
  const config = mode.config;
152
162
  const inputPatterns = resolveInputPatterns(config.input, selectors);
153
163
  const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
164
+ const duplicateSpecBasenames = await resolveDuplicateSpecBasenames(config.input);
154
165
  const snapshotEnabled = flags.snapshot !== false;
155
166
  const updateSnapshots = Boolean(flags.updateSnapshots);
156
167
  const cleanOutput = Boolean(flags.clean);
@@ -169,7 +180,11 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
169
180
  stdout: process.stdout,
170
181
  stderr: process.stderr,
171
182
  }));
172
- const command = runtimeCommand.split(" ")[0];
183
+ const runtimeTokens = tokenizeCommand(runtimeCommand);
184
+ if (!runtimeTokens.length) {
185
+ throw new Error("runtime command is empty");
186
+ }
187
+ const command = runtimeTokens[0];
173
188
  const execPath = getExec(command);
174
189
  if (!execPath) {
175
190
  const message = `${chalk.bgRed(" ERROR ")}${chalk.dim(":")} could not locate ${command} in PATH variable!`;
@@ -199,23 +214,25 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
199
214
  };
200
215
  for (let i = 0; i < inputFiles.length; i++) {
201
216
  const file = inputFiles[i];
202
- const outFile = path.join(config.outDir, resolveArtifactFileName(file, config.buildOptions.target, options.modeName));
217
+ const outFile = path.join(config.outDir, resolveArtifactFileName(file, config.buildOptions.target, options.modeName, duplicateSpecBasenames));
203
218
  const fileBase = file
204
219
  .slice(file.lastIndexOf("/") + 1)
205
220
  .replace(".ts", "")
206
221
  .replace(".spec", "");
207
- let cmd = runtimeCommand.replace(command, execPath);
208
- cmd = cmd.replace("<name>", fileBase);
209
- if (config.buildOptions.target == "bindings" && !cmd.includes("<file>")) {
210
- cmd = cmd.replace("<file>", resolveBindingsHelperPath(outFile));
211
- }
212
- else {
213
- cmd = cmd.replace("<file>", outFile);
214
- }
215
- const snapshotStore = new SnapshotStore(file, config.snapshotDir);
222
+ const fileToken = config.buildOptions.target == "bindings" &&
223
+ !runtimeTokens.some((token) => token.includes("<file>"))
224
+ ? resolveBindingsHelperPath(outFile)
225
+ : outFile;
226
+ const invocation = {
227
+ command: execPath,
228
+ args: runtimeTokens
229
+ .slice(1)
230
+ .map((token) => token.replace(/<name>/g, fileBase).replace(/<file>/g, fileToken)),
231
+ };
232
+ const snapshotStore = new SnapshotStore(file, config.snapshotDir, duplicateSpecBasenames);
216
233
  let report;
217
234
  try {
218
- report = await runProcess(cmd, snapshotStore, snapshotEnabled, updateSnapshots, reporter, reporterKind == "tap", mode.env);
235
+ report = await runProcess(invocation, snapshotStore, snapshotEnabled, updateSnapshots, reporter, reporterKind == "tap", mode.env);
219
236
  }
220
237
  catch (error) {
221
238
  const modeLabel = options.modeName ?? "default";
@@ -360,7 +377,8 @@ function fallbackToDefaultRuntime(runtimeRun, target, emitWarnings) {
360
377
  if (!fallback)
361
378
  return runtimeRun;
362
379
  const resolvedFallbackPath = path.join(process.cwd(), fallback.scriptPath);
363
- if (resolvedScriptPath == resolvedFallbackPath || scriptPath == fallback.scriptPath) {
380
+ if (resolvedScriptPath == resolvedFallbackPath ||
381
+ scriptPath == fallback.scriptPath) {
364
382
  return runtimeRun;
365
383
  }
366
384
  if (emitWarnings) {
@@ -521,15 +539,50 @@ try {
521
539
  }
522
540
  return null;
523
541
  }
524
- function resolveArtifactFileName(file, target, modeName) {
542
+ function resolveArtifactFileName(file, target, modeName, duplicateSpecBasenames = new Set()) {
525
543
  const base = path
526
544
  .basename(file)
527
545
  .replace(/\.spec\.ts$/, "")
528
546
  .replace(/\.ts$/, "");
529
- if (!modeName) {
530
- return `${path.basename(file).replace(".ts", ".wasm")}`;
531
- }
532
- return `${base}.${modeName}.${target}.wasm`;
547
+ const legacy = !modeName
548
+ ? `${path.basename(file).replace(".ts", ".wasm")}`
549
+ : `${base}.${modeName}.${target}.wasm`;
550
+ if (!duplicateSpecBasenames.has(path.basename(file))) {
551
+ return legacy;
552
+ }
553
+ const disambiguator = resolveDisambiguator(file, duplicateSpecBasenames);
554
+ if (!disambiguator.length) {
555
+ return legacy;
556
+ }
557
+ const ext = path.extname(legacy);
558
+ const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
559
+ return `${stem}.${disambiguator}${ext}`;
560
+ }
561
+ async function resolveDuplicateSpecBasenames(configured) {
562
+ const patterns = Array.isArray(configured) ? configured : [configured];
563
+ const files = await glob(patterns);
564
+ const counts = new Map();
565
+ for (const file of files) {
566
+ const base = path.basename(file);
567
+ counts.set(base, (counts.get(base) ?? 0) + 1);
568
+ }
569
+ const duplicates = new Set();
570
+ for (const [base, count] of counts) {
571
+ if (count > 1)
572
+ duplicates.add(base);
573
+ }
574
+ return duplicates;
575
+ }
576
+ function resolveDisambiguator(file, duplicateSpecBasenames) {
577
+ if (!duplicateSpecBasenames.has(path.basename(file)))
578
+ return "";
579
+ const relDir = path.dirname(path.relative(process.cwd(), file));
580
+ if (!relDir.length || relDir == ".")
581
+ return "";
582
+ return relDir
583
+ .replace(/[\\/]+/g, "__")
584
+ .replace(/[^A-Za-z0-9._-]/g, "_")
585
+ .replace(/^_+|_+$/g, "");
533
586
  }
534
587
  function resolveBindingsHelperPath(wasmPath) {
535
588
  const bindingsPath = wasmPath.replace(/\.wasm$/, ".bindings.js");
@@ -541,7 +594,10 @@ function resolveBindingsHelperPath(wasmPath) {
541
594
  return bindingsPath;
542
595
  }
543
596
  function extractRuntimeScriptPath(runtimeRun) {
544
- const tokens = runtimeRun.trim().split(/\s+/).filter((token) => token.length > 0);
597
+ const tokens = runtimeRun
598
+ .trim()
599
+ .split(/\s+/)
600
+ .filter((token) => token.length > 0);
545
601
  if (tokens.length < 2)
546
602
  return null;
547
603
  const execToken = path.basename(tokens[0]).toLowerCase();
@@ -614,7 +670,9 @@ function runtimeNameFromCommand(command) {
614
670
  return token && token.length ? token : "runtime";
615
671
  }
616
672
  function resolveInputPatterns(configured, selectors) {
617
- const configuredInputs = Array.isArray(configured) ? configured : [configured];
673
+ const configuredInputs = Array.isArray(configured)
674
+ ? configured
675
+ : [configured];
618
676
  if (!selectors.length)
619
677
  return configuredInputs;
620
678
  const patterns = new Set();
@@ -871,16 +929,20 @@ function compareCoveragePoints(a, b) {
871
929
  return a.type.localeCompare(b.type);
872
930
  return a.hash.localeCompare(b.hash);
873
931
  }
874
- async function runProcess(cmd, snapshots, snapshotEnabled, updateSnapshots, reporter, tapMode = false, env = process.env) {
875
- const child = spawn(cmd, {
932
+ async function runProcess(invocation, snapshots, snapshotEnabled, updateSnapshots, reporter, tapMode = false, env = process.env) {
933
+ const child = spawn(invocation.command, invocation.args, {
876
934
  stdio: ["pipe", "pipe", "pipe"],
877
- shell: true,
935
+ shell: false,
878
936
  env,
879
937
  });
880
938
  let report = null;
881
939
  let parseError = null;
882
940
  let stderrBuffer = "";
883
941
  let suppressTraceWarningLine = false;
942
+ let spawnError = null;
943
+ child.on("error", (error) => {
944
+ spawnError = error;
945
+ });
884
946
  child.stderr.on("data", (chunk) => {
885
947
  stderrBuffer += chunk.toString("utf8");
886
948
  let newline = stderrBuffer.indexOf("\n");
@@ -988,6 +1050,9 @@ async function runProcess(cmd, snapshots, snapshotEnabled, updateSnapshots, repo
988
1050
  process.stderr.write(stderrBuffer);
989
1051
  }
990
1052
  }
1053
+ if (spawnError) {
1054
+ throw spawnError;
1055
+ }
991
1056
  if (parseError) {
992
1057
  throw new Error(`could not parse report payload: ${parseError}`);
993
1058
  }
@@ -0,0 +1,20 @@
1
+ export { createRunReporter, run } from "./run-core.js";
2
+ export async function executeRunCommand(rawArgs, flags, configPath, selectedModes, deps) {
3
+ const commandArgs = deps.resolveCommandArgs(rawArgs, "run");
4
+ const listFlags = deps.resolveListFlags(rawArgs, "run");
5
+ const featureToggles = deps.resolveFeatureToggles(rawArgs, "run");
6
+ const runFlags = {
7
+ snapshot: !flags.includes("--no-snapshot"),
8
+ updateSnapshots: flags.includes("--update-snapshots"),
9
+ clean: flags.includes("--clean"),
10
+ showCoverage: flags.includes("--show-coverage"),
11
+ verbose: flags.includes("--verbose"),
12
+ coverage: featureToggles.coverage,
13
+ };
14
+ const modeTargets = deps.resolveExecutionModes(configPath, selectedModes);
15
+ if (listFlags.list || listFlags.listModes) {
16
+ await deps.listExecutionPlan("run", configPath, commandArgs, modeTargets, listFlags);
17
+ return;
18
+ }
19
+ await deps.runRuntimeModes(runFlags, configPath, commandArgs, modeTargets);
20
+ }
@@ -0,0 +1,23 @@
1
+ export async function executeTestCommand(rawArgs, flags, configPath, selectedModes, deps) {
2
+ const commandArgs = deps.resolveCommandArgs(rawArgs, "test");
3
+ const listFlags = deps.resolveListFlags(rawArgs, "test");
4
+ const featureToggles = deps.resolveFeatureToggles(rawArgs, "test");
5
+ const buildFeatureToggles = {
6
+ tryAs: featureToggles.tryAs,
7
+ coverage: featureToggles.coverage,
8
+ };
9
+ const runFlags = {
10
+ snapshot: !flags.includes("--no-snapshot"),
11
+ updateSnapshots: flags.includes("--update-snapshots"),
12
+ clean: flags.includes("--clean"),
13
+ showCoverage: flags.includes("--show-coverage"),
14
+ verbose: flags.includes("--verbose"),
15
+ coverage: featureToggles.coverage,
16
+ };
17
+ const modeTargets = deps.resolveExecutionModes(configPath, selectedModes);
18
+ if (listFlags.list || listFlags.listModes) {
19
+ await deps.listExecutionPlan("test", configPath, commandArgs, modeTargets, listFlags);
20
+ return;
21
+ }
22
+ await deps.runTestModes(runFlags, configPath, commandArgs, modeTargets, buildFeatureToggles);
23
+ }
@@ -0,0 +1 @@
1
+ export {};