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.
- package/CHANGELOG.md +22 -0
- package/README.md +74 -7
- package/as-test.config.schema.json +31 -0
- package/assembly/src/expectation.ts +32 -9
- package/bin/{build.js → commands/build-core.js} +113 -35
- package/bin/commands/build.js +16 -0
- package/bin/commands/doctor-core.js +335 -0
- package/bin/commands/doctor.js +5 -0
- package/bin/commands/init-core.js +991 -0
- package/bin/commands/init.js +6 -0
- package/bin/{run.js → commands/run-core.js} +95 -30
- package/bin/commands/run.js +20 -0
- package/bin/commands/test.js +23 -0
- package/bin/commands/types.js +1 -0
- package/bin/index.js +410 -52
- package/bin/util.js +573 -8
- package/package.json +8 -5
- package/transform/lib/index.js +2 -1
- package/bin/init.js +0 -497
|
@@ -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 "
|
|
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 "
|
|
9
|
-
import { createTapReporter } from "
|
|
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, `${
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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(
|
|
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 ||
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
|
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)
|
|
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(
|
|
875
|
-
const child = spawn(
|
|
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:
|
|
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 {};
|