as-test 0.5.1 → 0.5.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.
- package/CHANGELOG.md +101 -0
- package/README.md +268 -3
- package/as-test.config.schema.json +171 -2
- package/assembly/coverage.ts +20 -0
- package/assembly/index.ts +196 -12
- package/assembly/src/expectation.ts +15 -29
- package/assembly/src/log.ts +13 -1
- package/assembly/src/suite.ts +53 -9
- package/assembly/src/tests.ts +25 -5
- package/assembly/util/helpers.ts +0 -1
- package/assembly/util/json.ts +78 -0
- package/bin/build.js +118 -33
- package/bin/index.js +524 -35
- package/bin/init.js +35 -10
- package/bin/reporters/default.js +26 -9
- package/bin/reporters/tap.js +294 -0
- package/bin/run.js +368 -44
- package/bin/types.js +18 -0
- package/bin/util.js +229 -1
- package/package.json +40 -50
- package/transform/lib/coverage.js +135 -124
- package/transform/lib/index.js +57 -23
- package/transform/lib/log.js +2 -39
- package/transform/lib/mock.js +42 -22
- package/transform/lib/builder.js.map +0 -1
- package/transform/lib/coverage.js.map +0 -1
- package/transform/lib/index.js.map +0 -1
- package/transform/lib/linker.js.map +0 -1
- package/transform/lib/location.js.map +0 -1
- package/transform/lib/log.js.map +0 -1
- package/transform/lib/mock.js.map +0 -1
- package/transform/lib/range.js.map +0 -1
- package/transform/lib/types.js.map +0 -1
- package/transform/lib/util.js.map +0 -1
- package/transform/lib/visitor.js.map +0 -1
package/bin/run.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import { glob } from "glob";
|
|
4
|
-
import { getExec, loadConfig } from "./util.js";
|
|
4
|
+
import { applyMode, getExec, loadConfig } 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
8
|
import { createReporter as createDefaultReporter } from "./reporters/default.js";
|
|
9
|
+
import { createTapReporter } from "./reporters/tap.js";
|
|
9
10
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
10
11
|
var MessageType;
|
|
11
12
|
(function (MessageType) {
|
|
@@ -145,19 +146,26 @@ class SnapshotStore {
|
|
|
145
146
|
export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selectors = [], shouldExit = true, options = {}) {
|
|
146
147
|
const resolvedConfigPath = configPath ?? DEFAULT_CONFIG_PATH;
|
|
147
148
|
const reports = [];
|
|
148
|
-
const
|
|
149
|
+
const loadedConfig = loadConfig(resolvedConfigPath);
|
|
150
|
+
const mode = applyMode(loadedConfig, options.modeName);
|
|
151
|
+
const config = mode.config;
|
|
149
152
|
const inputPatterns = resolveInputPatterns(config.input, selectors);
|
|
150
|
-
const inputFiles = await glob(inputPatterns);
|
|
153
|
+
const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
|
|
151
154
|
const snapshotEnabled = flags.snapshot !== false;
|
|
152
155
|
const updateSnapshots = Boolean(flags.updateSnapshots);
|
|
153
156
|
const cleanOutput = Boolean(flags.clean);
|
|
154
157
|
const showCoverage = Boolean(flags.showCoverage);
|
|
155
158
|
const coverage = resolveCoverageOptions(config.coverage);
|
|
159
|
+
if (flags.coverage != undefined) {
|
|
160
|
+
coverage.enabled = Boolean(flags.coverage);
|
|
161
|
+
}
|
|
156
162
|
const coverageEnabled = coverage.enabled;
|
|
157
163
|
const coverageDir = config.coverageDir ?? "./.as-test/coverage";
|
|
158
164
|
const runtimeCommand = resolveRuntimeCommand(getConfiguredRuntimeCmd(config), config.buildOptions.target);
|
|
165
|
+
const reporterSelection = resolveReporterSelection(options.reporterPath, config.runOptions.reporter);
|
|
166
|
+
const reporterKind = options.reporterKind ?? reporterSelection.kind;
|
|
159
167
|
const reporter = options.reporter ??
|
|
160
|
-
(await loadReporter(
|
|
168
|
+
(await loadReporter(reporterSelection, resolvedConfigPath, {
|
|
161
169
|
stdout: process.stdout,
|
|
162
170
|
stderr: process.stderr,
|
|
163
171
|
}));
|
|
@@ -191,7 +199,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
191
199
|
};
|
|
192
200
|
for (let i = 0; i < inputFiles.length; i++) {
|
|
193
201
|
const file = inputFiles[i];
|
|
194
|
-
const outFile = path.join(config.outDir,
|
|
202
|
+
const outFile = path.join(config.outDir, resolveArtifactFileName(file, config.buildOptions.target, options.modeName));
|
|
195
203
|
const fileBase = file
|
|
196
204
|
.slice(file.lastIndexOf("/") + 1)
|
|
197
205
|
.replace(".ts", "")
|
|
@@ -199,16 +207,21 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
199
207
|
let cmd = runtimeCommand.replace(command, execPath);
|
|
200
208
|
cmd = cmd.replace("<name>", fileBase);
|
|
201
209
|
if (config.buildOptions.target == "bindings" && !cmd.includes("<file>")) {
|
|
202
|
-
cmd = cmd.replace("<file>", outFile
|
|
203
|
-
.replace("build", "tests")
|
|
204
|
-
.replace(".spec", "")
|
|
205
|
-
.replace(".wasm", ".run.js"));
|
|
210
|
+
cmd = cmd.replace("<file>", resolveBindingsHelperPath(outFile));
|
|
206
211
|
}
|
|
207
212
|
else {
|
|
208
213
|
cmd = cmd.replace("<file>", outFile);
|
|
209
214
|
}
|
|
210
215
|
const snapshotStore = new SnapshotStore(file, config.snapshotDir);
|
|
211
|
-
|
|
216
|
+
let report;
|
|
217
|
+
try {
|
|
218
|
+
report = await runProcess(cmd, snapshotStore, snapshotEnabled, updateSnapshots, reporter, reporterKind == "tap", mode.env);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
const modeLabel = options.modeName ?? "default";
|
|
222
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
223
|
+
throw new Error(`Failed to run ${path.basename(file)} in mode ${modeLabel} with ${details}`);
|
|
224
|
+
}
|
|
212
225
|
const normalized = normalizeReport(report);
|
|
213
226
|
snapshotStore.flush();
|
|
214
227
|
snapshotSummary.matched += snapshotStore.matched;
|
|
@@ -232,6 +245,9 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
232
245
|
writeFileSync(path.join(process.cwd(), config.logs, options.logFileName ?? "test.log.json"), JSON.stringify(logReports, null, 2));
|
|
233
246
|
}
|
|
234
247
|
const stats = collectRunStats(reports.map((report) => report.suites));
|
|
248
|
+
if (options.fileSummaryTotal != undefined) {
|
|
249
|
+
applyConfiguredFileTotalToStats(stats, options.fileSummaryTotal);
|
|
250
|
+
}
|
|
235
251
|
const coverageSummary = collectCoverageSummary(reports, coverageEnabled, showCoverage, coverage);
|
|
236
252
|
if (coverageEnabled &&
|
|
237
253
|
coverageDir &&
|
|
@@ -244,6 +260,10 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
244
260
|
writeFileSync(path.join(resolvedCoverageDir, options.coverageFileName ?? "coverage.log.json"), JSON.stringify(coverageSummary, null, 2));
|
|
245
261
|
}
|
|
246
262
|
if (options.emitRunComplete !== false) {
|
|
263
|
+
const totalModes = Math.max(options.modeSummaryTotal ?? 1, 1);
|
|
264
|
+
const executedModes = Math.min(Math.max(options.modeSummaryExecuted ?? 1, 1), totalModes);
|
|
265
|
+
const unexecutedModes = Math.max(0, totalModes - executedModes);
|
|
266
|
+
const modeFailed = Boolean(stats.failedFiles || snapshotSummary.failed);
|
|
247
267
|
reporter.onRunComplete?.({
|
|
248
268
|
clean: cleanOutput,
|
|
249
269
|
snapshotEnabled,
|
|
@@ -252,6 +272,11 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
252
272
|
coverageSummary,
|
|
253
273
|
stats,
|
|
254
274
|
reports,
|
|
275
|
+
modeSummary: {
|
|
276
|
+
failed: modeFailed ? 1 : 0,
|
|
277
|
+
skipped: unexecutedModes + (modeFailed || stats.passedFiles > 0 ? 0 : 1),
|
|
278
|
+
total: totalModes,
|
|
279
|
+
},
|
|
255
280
|
});
|
|
256
281
|
}
|
|
257
282
|
const failed = Boolean(stats.failedFiles || snapshotSummary.failed);
|
|
@@ -266,35 +291,59 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
266
291
|
reports,
|
|
267
292
|
};
|
|
268
293
|
}
|
|
294
|
+
function applyConfiguredFileTotalToStats(stats, fileSummaryTotal) {
|
|
295
|
+
const total = Math.max(fileSummaryTotal, 0);
|
|
296
|
+
const executed = stats.failedFiles + stats.passedFiles + stats.skippedFiles;
|
|
297
|
+
const unexecuted = Math.max(0, total - executed);
|
|
298
|
+
stats.skippedFiles += unexecuted;
|
|
299
|
+
}
|
|
269
300
|
function resolveRuntimeCommand(runtimeRun, target, emitWarnings = true) {
|
|
270
|
-
const normalized =
|
|
301
|
+
const normalized = resolveLegacyRuntime(runtimeRun, target, emitWarnings);
|
|
271
302
|
return fallbackToDefaultRuntime(normalized, target, emitWarnings);
|
|
272
303
|
}
|
|
273
|
-
function
|
|
274
|
-
if (target
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const resolvedPreferredPath = path.join(process.cwd(), preferredPath);
|
|
280
|
-
if (existsSync(resolvedPreferredPath))
|
|
304
|
+
function resolveLegacyRuntime(runtimeRun, target, emitWarnings) {
|
|
305
|
+
if (target == "wasi") {
|
|
306
|
+
const preferredPath = "./.as-test/runners/default.wasi.js";
|
|
307
|
+
const legacyPaths = ["./bin/wasi-run.js", "./.as-test/wasi/wasi.run.js"];
|
|
308
|
+
if (runtimeRun.includes(preferredPath)) {
|
|
309
|
+
ensureDefaultRuntimeRunner("wasi", emitWarnings);
|
|
281
310
|
return runtimeRun;
|
|
282
|
-
|
|
311
|
+
}
|
|
312
|
+
for (const legacyPath of legacyPaths) {
|
|
313
|
+
if (!runtimeRun.includes(legacyPath))
|
|
314
|
+
continue;
|
|
315
|
+
const resolvedLegacyPath = path.join(process.cwd(), legacyPath);
|
|
316
|
+
if (existsSync(resolvedLegacyPath))
|
|
317
|
+
return runtimeRun;
|
|
318
|
+
ensureDefaultRuntimeRunner("wasi", emitWarnings);
|
|
319
|
+
if (emitWarnings) {
|
|
320
|
+
process.stderr.write(chalk.dim(`legacy WASI runtime path detected (${legacyPath}); using ${preferredPath}\n`));
|
|
321
|
+
}
|
|
322
|
+
return runtimeRun.replace(legacyPath, preferredPath);
|
|
323
|
+
}
|
|
324
|
+
return runtimeRun;
|
|
283
325
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
326
|
+
if (target == "bindings") {
|
|
327
|
+
const preferredPath = "./.as-test/runners/default.bindings.js";
|
|
328
|
+
const legacyPath = "./.as-test/runners/default.run.js";
|
|
329
|
+
if (runtimeRun.includes(preferredPath)) {
|
|
330
|
+
ensureDefaultRuntimeRunner("bindings", emitWarnings);
|
|
289
331
|
return runtimeRun;
|
|
290
|
-
|
|
291
|
-
if (
|
|
332
|
+
}
|
|
333
|
+
if (runtimeRun.includes(legacyPath)) {
|
|
334
|
+
const resolvedLegacyPath = path.join(process.cwd(), legacyPath);
|
|
335
|
+
if (existsSync(resolvedLegacyPath)) {
|
|
336
|
+
if (emitWarnings) {
|
|
337
|
+
process.stderr.write(chalk.dim(`deprecated runtime script (${legacyPath}) detected; prefer ${preferredPath}\n`));
|
|
338
|
+
}
|
|
339
|
+
return runtimeRun;
|
|
340
|
+
}
|
|
341
|
+
ensureDefaultRuntimeRunner("bindings", emitWarnings);
|
|
292
342
|
if (emitWarnings) {
|
|
293
|
-
process.stderr.write(chalk.dim(`legacy
|
|
343
|
+
process.stderr.write(chalk.dim(`legacy bindings runtime path detected (${legacyPath}); using ${preferredPath}\n`));
|
|
294
344
|
}
|
|
295
345
|
return runtimeRun.replace(legacyPath, preferredPath);
|
|
296
346
|
}
|
|
297
|
-
throw new Error(`could not locate WASI runner. Expected ${legacyPath} or ${preferredPath}. Run "ast init --target wasi --force --yes" to scaffold the local runner.`);
|
|
298
347
|
}
|
|
299
348
|
return runtimeRun;
|
|
300
349
|
}
|
|
@@ -307,15 +356,12 @@ function fallbackToDefaultRuntime(runtimeRun, target, emitWarnings) {
|
|
|
307
356
|
: path.join(process.cwd(), scriptPath);
|
|
308
357
|
if (existsSync(resolvedScriptPath))
|
|
309
358
|
return runtimeRun;
|
|
310
|
-
const fallback =
|
|
359
|
+
const fallback = ensureDefaultRuntimeRunner(target, emitWarnings);
|
|
311
360
|
if (!fallback)
|
|
312
361
|
return runtimeRun;
|
|
313
362
|
const resolvedFallbackPath = path.join(process.cwd(), fallback.scriptPath);
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
throw new Error(`could not locate runtime script at ${fallback.scriptPath}. Run "ast init --target ${target} --force --yes" to scaffold the local runner.`);
|
|
317
|
-
}
|
|
318
|
-
throw new Error(`could not locate runtime script at ${scriptPath}. Default runner ${fallback.scriptPath} is also missing. Run "ast init --target ${target} --force --yes" to scaffold it.`);
|
|
363
|
+
if (resolvedScriptPath == resolvedFallbackPath || scriptPath == fallback.scriptPath) {
|
|
364
|
+
return runtimeRun;
|
|
319
365
|
}
|
|
320
366
|
if (emitWarnings) {
|
|
321
367
|
process.stderr.write(chalk.dim(`runtime script not found (${scriptPath}); using ${fallback.scriptPath}\n`));
|
|
@@ -331,12 +377,169 @@ function getDefaultRuntimeFallback(target) {
|
|
|
331
377
|
}
|
|
332
378
|
if (target == "bindings") {
|
|
333
379
|
return {
|
|
334
|
-
command: "node ./.as-test/runners/default.
|
|
335
|
-
scriptPath: "./.as-test/runners/default.
|
|
380
|
+
command: "node ./.as-test/runners/default.bindings.js <file>",
|
|
381
|
+
scriptPath: "./.as-test/runners/default.bindings.js",
|
|
336
382
|
};
|
|
337
383
|
}
|
|
338
384
|
return null;
|
|
339
385
|
}
|
|
386
|
+
function ensureDefaultRuntimeRunner(target, emitWarnings) {
|
|
387
|
+
const fallback = getDefaultRuntimeFallback(target);
|
|
388
|
+
if (!fallback)
|
|
389
|
+
return null;
|
|
390
|
+
const resolvedScriptPath = path.join(process.cwd(), fallback.scriptPath);
|
|
391
|
+
if (existsSync(resolvedScriptPath))
|
|
392
|
+
return fallback;
|
|
393
|
+
const source = getDefaultRuntimeRunnerSource(target);
|
|
394
|
+
if (!source)
|
|
395
|
+
return fallback;
|
|
396
|
+
if (!existsSync(path.dirname(resolvedScriptPath))) {
|
|
397
|
+
mkdirSync(path.dirname(resolvedScriptPath), { recursive: true });
|
|
398
|
+
}
|
|
399
|
+
writeFileSync(resolvedScriptPath, source);
|
|
400
|
+
if (emitWarnings) {
|
|
401
|
+
process.stderr.write(chalk.dim(`runtime script missing; created ${fallback.scriptPath}\n`));
|
|
402
|
+
}
|
|
403
|
+
return fallback;
|
|
404
|
+
}
|
|
405
|
+
function getDefaultRuntimeRunnerSource(target) {
|
|
406
|
+
if (target == "wasi") {
|
|
407
|
+
return `import { readFileSync } from "fs";
|
|
408
|
+
import { WASI } from "wasi";
|
|
409
|
+
|
|
410
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
411
|
+
process.emitWarning = ((warning, ...args) => {
|
|
412
|
+
const type = typeof args[0] == "string" ? args[0] : "";
|
|
413
|
+
const name = typeof warning?.name == "string" ? warning.name : type;
|
|
414
|
+
const message =
|
|
415
|
+
typeof warning == "string" ? warning : String(warning?.message ?? "");
|
|
416
|
+
if (
|
|
417
|
+
name == "ExperimentalWarning" &&
|
|
418
|
+
message.includes("WASI is an experimental feature")
|
|
419
|
+
) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
return originalEmitWarning(warning, ...args);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const wasmPath = process.argv[2];
|
|
426
|
+
if (!wasmPath) {
|
|
427
|
+
process.stderr.write("usage: node ./.as-test/runners/default.wasi.js <file.wasm>\\n");
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
const wasi = new WASI({
|
|
433
|
+
version: "preview1",
|
|
434
|
+
args: [wasmPath],
|
|
435
|
+
env: process.env,
|
|
436
|
+
preopens: {},
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const binary = readFileSync(wasmPath);
|
|
440
|
+
const module = new WebAssembly.Module(binary);
|
|
441
|
+
const instance = new WebAssembly.Instance(module, {
|
|
442
|
+
wasi_snapshot_preview1: wasi.wasiImport,
|
|
443
|
+
});
|
|
444
|
+
wasi.start(instance);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
process.stderr.write("failed to run WASI module: " + String(error) + "\\n");
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
if (target == "bindings") {
|
|
452
|
+
return `import fs from "fs";
|
|
453
|
+
import path from "path";
|
|
454
|
+
import { pathToFileURL } from "url";
|
|
455
|
+
|
|
456
|
+
let patched = false;
|
|
457
|
+
|
|
458
|
+
function readExact(length) {
|
|
459
|
+
const out = Buffer.alloc(length);
|
|
460
|
+
let offset = 0;
|
|
461
|
+
while (offset < length) {
|
|
462
|
+
let read = 0;
|
|
463
|
+
try {
|
|
464
|
+
read = fs.readSync(0, out, offset, length - offset, null);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
if (error && error.code === "EAGAIN") {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
if (!read) break;
|
|
472
|
+
offset += read;
|
|
473
|
+
}
|
|
474
|
+
const view = out.subarray(0, offset);
|
|
475
|
+
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function writeRaw(data) {
|
|
479
|
+
const view = Buffer.from(data);
|
|
480
|
+
fs.writeSync(1, view);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function withNodeIo(imports = {}) {
|
|
484
|
+
if (!patched) {
|
|
485
|
+
patched = true;
|
|
486
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
487
|
+
process.stdout.write = (chunk, ...args) => {
|
|
488
|
+
if (chunk instanceof ArrayBuffer) {
|
|
489
|
+
writeRaw(chunk);
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
return originalWrite(chunk, ...args);
|
|
493
|
+
};
|
|
494
|
+
process.stdin.read = (size) => readExact(Number(size ?? 0));
|
|
495
|
+
}
|
|
496
|
+
return imports;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const wasmPathArg = process.argv[2];
|
|
500
|
+
if (!wasmPathArg) {
|
|
501
|
+
process.stderr.write("usage: node ./.as-test/runners/default.bindings.js <file.wasm>\\n");
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const wasmPath = path.resolve(process.cwd(), wasmPathArg);
|
|
506
|
+
const jsPath = wasmPath.replace(/\\.wasm$/, ".js");
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const binary = fs.readFileSync(wasmPath);
|
|
510
|
+
const module = new WebAssembly.Module(binary);
|
|
511
|
+
const mod = await import(pathToFileURL(jsPath).href);
|
|
512
|
+
if (typeof mod.instantiate !== "function") {
|
|
513
|
+
throw new Error("bindings helper missing instantiate export");
|
|
514
|
+
}
|
|
515
|
+
mod.instantiate(module, withNodeIo({}));
|
|
516
|
+
} catch (error) {
|
|
517
|
+
process.stderr.write("failed to run bindings module: " + String(error) + "\\n");
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
`;
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
function resolveArtifactFileName(file, target, modeName) {
|
|
525
|
+
const base = path
|
|
526
|
+
.basename(file)
|
|
527
|
+
.replace(/\.spec\.ts$/, "")
|
|
528
|
+
.replace(/\.ts$/, "");
|
|
529
|
+
if (!modeName) {
|
|
530
|
+
return `${path.basename(file).replace(".ts", ".wasm")}`;
|
|
531
|
+
}
|
|
532
|
+
return `${base}.${modeName}.${target}.wasm`;
|
|
533
|
+
}
|
|
534
|
+
function resolveBindingsHelperPath(wasmPath) {
|
|
535
|
+
const bindingsPath = wasmPath.replace(/\.wasm$/, ".bindings.js");
|
|
536
|
+
if (existsSync(bindingsPath))
|
|
537
|
+
return bindingsPath;
|
|
538
|
+
const legacyRunPath = wasmPath.replace(/\.wasm$/, ".run.js");
|
|
539
|
+
if (existsSync(legacyRunPath))
|
|
540
|
+
return legacyRunPath;
|
|
541
|
+
return bindingsPath;
|
|
542
|
+
}
|
|
340
543
|
function extractRuntimeScriptPath(runtimeRun) {
|
|
341
544
|
const tokens = runtimeRun.trim().split(/\s+/).filter((token) => token.length > 0);
|
|
342
545
|
if (tokens.length < 2)
|
|
@@ -415,7 +618,7 @@ function resolveInputPatterns(configured, selectors) {
|
|
|
415
618
|
if (!selectors.length)
|
|
416
619
|
return configuredInputs;
|
|
417
620
|
const patterns = new Set();
|
|
418
|
-
for (const selector of selectors) {
|
|
621
|
+
for (const selector of expandSelectors(selectors)) {
|
|
419
622
|
if (!selector)
|
|
420
623
|
continue;
|
|
421
624
|
if (isBareSuiteSelector(selector)) {
|
|
@@ -429,6 +632,30 @@ function resolveInputPatterns(configured, selectors) {
|
|
|
429
632
|
}
|
|
430
633
|
return [...patterns];
|
|
431
634
|
}
|
|
635
|
+
function expandSelectors(selectors) {
|
|
636
|
+
const expanded = [];
|
|
637
|
+
for (const selector of selectors) {
|
|
638
|
+
if (!selector)
|
|
639
|
+
continue;
|
|
640
|
+
if (!shouldSplitSelector(selector)) {
|
|
641
|
+
expanded.push(selector);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
for (const token of selector.split(",")) {
|
|
645
|
+
const trimmed = token.trim();
|
|
646
|
+
if (!trimmed.length)
|
|
647
|
+
continue;
|
|
648
|
+
expanded.push(trimmed);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return expanded;
|
|
652
|
+
}
|
|
653
|
+
function shouldSplitSelector(selector) {
|
|
654
|
+
return (selector.includes(",") &&
|
|
655
|
+
!selector.includes("/") &&
|
|
656
|
+
!selector.includes("\\") &&
|
|
657
|
+
!/[*?[\]{}]/.test(selector));
|
|
658
|
+
}
|
|
432
659
|
function isBareSuiteSelector(selector) {
|
|
433
660
|
return (!selector.includes("/") &&
|
|
434
661
|
!selector.includes("\\") &&
|
|
@@ -644,10 +871,11 @@ function compareCoveragePoints(a, b) {
|
|
|
644
871
|
return a.type.localeCompare(b.type);
|
|
645
872
|
return a.hash.localeCompare(b.hash);
|
|
646
873
|
}
|
|
647
|
-
async function runProcess(cmd, snapshots, snapshotEnabled, updateSnapshots, reporter) {
|
|
874
|
+
async function runProcess(cmd, snapshots, snapshotEnabled, updateSnapshots, reporter, tapMode = false, env = process.env) {
|
|
648
875
|
const child = spawn(cmd, {
|
|
649
876
|
stdio: ["pipe", "pipe", "pipe"],
|
|
650
877
|
shell: true,
|
|
878
|
+
env,
|
|
651
879
|
});
|
|
652
880
|
let report = null;
|
|
653
881
|
let parseError = null;
|
|
@@ -671,7 +899,12 @@ async function runProcess(cmd, snapshots, snapshotEnabled, updateSnapshots, repo
|
|
|
671
899
|
});
|
|
672
900
|
class TestChannel extends Channel {
|
|
673
901
|
onPassthrough(data) {
|
|
674
|
-
|
|
902
|
+
if (tapMode) {
|
|
903
|
+
process.stderr.write(data);
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
process.stdout.write(data);
|
|
907
|
+
}
|
|
675
908
|
}
|
|
676
909
|
onCall(msg) {
|
|
677
910
|
const event = msg;
|
|
@@ -871,21 +1104,32 @@ function mergeVerdict(current, next) {
|
|
|
871
1104
|
return "skip";
|
|
872
1105
|
return "none";
|
|
873
1106
|
}
|
|
874
|
-
export async function createRunReporter(configPath = DEFAULT_CONFIG_PATH) {
|
|
1107
|
+
export async function createRunReporter(configPath = DEFAULT_CONFIG_PATH, reporterPath, modeName) {
|
|
875
1108
|
const resolvedConfigPath = configPath ?? DEFAULT_CONFIG_PATH;
|
|
876
|
-
const
|
|
877
|
-
const
|
|
1109
|
+
const loadedConfig = loadConfig(resolvedConfigPath);
|
|
1110
|
+
const mode = applyMode(loadedConfig, modeName);
|
|
1111
|
+
const config = mode.config;
|
|
1112
|
+
const selection = resolveReporterSelection(reporterPath, config.runOptions.reporter);
|
|
1113
|
+
const reporter = await loadReporter(selection, resolvedConfigPath, {
|
|
878
1114
|
stdout: process.stdout,
|
|
879
1115
|
stderr: process.stderr,
|
|
880
1116
|
});
|
|
881
1117
|
const runtimeCommand = resolveRuntimeCommand(getConfiguredRuntimeCmd(config), config.buildOptions.target, false);
|
|
882
1118
|
return {
|
|
883
1119
|
reporter,
|
|
1120
|
+
reporterKind: selection.kind,
|
|
884
1121
|
runtimeName: runtimeNameFromCommand(runtimeCommand),
|
|
885
1122
|
resolvedConfigPath,
|
|
886
1123
|
};
|
|
887
1124
|
}
|
|
888
|
-
async function loadReporter(
|
|
1125
|
+
async function loadReporter(selection, configPath, context) {
|
|
1126
|
+
if (selection.kind == "default") {
|
|
1127
|
+
return createDefaultReporter(context);
|
|
1128
|
+
}
|
|
1129
|
+
if (selection.kind == "tap") {
|
|
1130
|
+
return createTapReporter(context, selection.tap);
|
|
1131
|
+
}
|
|
1132
|
+
const reporterPath = selection.reporterPath;
|
|
889
1133
|
if (!reporterPath) {
|
|
890
1134
|
return createDefaultReporter(context);
|
|
891
1135
|
}
|
|
@@ -903,6 +1147,86 @@ async function loadReporter(reporterPath, configPath, context) {
|
|
|
903
1147
|
throw reporterError;
|
|
904
1148
|
}
|
|
905
1149
|
}
|
|
1150
|
+
function resolveReporterSelection(cliValue, configValue) {
|
|
1151
|
+
const parsed = parseReporterConfig(configValue);
|
|
1152
|
+
const raw = resolveCliReporter(process.argv.slice(2), cliValue ?? parsed.name);
|
|
1153
|
+
const normalized = raw.trim();
|
|
1154
|
+
const canonical = normalized.toLowerCase();
|
|
1155
|
+
if (!normalized.length || canonical == "default") {
|
|
1156
|
+
return { kind: "default", reporterPath: "", tap: parsed.tap };
|
|
1157
|
+
}
|
|
1158
|
+
if (canonical == "tap" || canonical == "tap13") {
|
|
1159
|
+
return { kind: "tap", reporterPath: "", tap: parsed.tap };
|
|
1160
|
+
}
|
|
1161
|
+
return { kind: "custom", reporterPath: normalized, tap: parsed.tap };
|
|
1162
|
+
}
|
|
1163
|
+
function resolveCliReporter(argv, fallback) {
|
|
1164
|
+
let resolved = fallback;
|
|
1165
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1166
|
+
const token = argv[i];
|
|
1167
|
+
if (token == "--tap") {
|
|
1168
|
+
resolved = "tap";
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (token == "--reporter") {
|
|
1172
|
+
const value = argv[i + 1];
|
|
1173
|
+
if (!value || value.startsWith("-")) {
|
|
1174
|
+
throw new Error(`--reporter requires a value`);
|
|
1175
|
+
}
|
|
1176
|
+
resolved = value;
|
|
1177
|
+
i++;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
if (token.startsWith("--reporter=")) {
|
|
1181
|
+
const value = token.slice("--reporter=".length);
|
|
1182
|
+
if (!value.length) {
|
|
1183
|
+
throw new Error(`--reporter requires a value`);
|
|
1184
|
+
}
|
|
1185
|
+
resolved = value;
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return resolved;
|
|
1190
|
+
}
|
|
1191
|
+
function parseReporterConfig(value) {
|
|
1192
|
+
const tap = {
|
|
1193
|
+
mode: "single-file",
|
|
1194
|
+
outDir: "./.as-test/reports",
|
|
1195
|
+
outFile: "./.as-test/reports/report.tap",
|
|
1196
|
+
};
|
|
1197
|
+
if (typeof value == "string") {
|
|
1198
|
+
return { name: value, tap };
|
|
1199
|
+
}
|
|
1200
|
+
if (!value || typeof value != "object") {
|
|
1201
|
+
return { name: "", tap };
|
|
1202
|
+
}
|
|
1203
|
+
const config = value;
|
|
1204
|
+
const name = typeof config.name == "string" ? config.name : "";
|
|
1205
|
+
if (typeof config.outDir == "string" && config.outDir.trim().length) {
|
|
1206
|
+
tap.outDir = config.outDir.trim();
|
|
1207
|
+
}
|
|
1208
|
+
if (typeof config.outFile == "string" && config.outFile.trim().length) {
|
|
1209
|
+
tap.outFile = config.outFile.trim();
|
|
1210
|
+
}
|
|
1211
|
+
else if (tap.outDir && tap.outDir.length) {
|
|
1212
|
+
tap.outFile = path.join(tap.outDir, "report.tap");
|
|
1213
|
+
}
|
|
1214
|
+
if (Array.isArray(config.options)) {
|
|
1215
|
+
const options = config.options
|
|
1216
|
+
.filter((option) => typeof option == "string")
|
|
1217
|
+
.map((option) => option.toLowerCase());
|
|
1218
|
+
if (options.includes("per-file")) {
|
|
1219
|
+
tap.mode = "per-file";
|
|
1220
|
+
if (!config.outFile && tap.outDir && tap.outDir.length) {
|
|
1221
|
+
tap.outFile = path.join(tap.outDir, "report.tap");
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
tap.mode = "single-file";
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return { name, tap };
|
|
1229
|
+
}
|
|
906
1230
|
function resolveReporterFactory(mod) {
|
|
907
1231
|
const fromNamed = mod.createReporter;
|
|
908
1232
|
if (typeof fromNamed == "function") {
|
package/bin/types.js
CHANGED
|
@@ -8,8 +8,10 @@ export class Config {
|
|
|
8
8
|
this.snapshotDir = "./.as-test/snapshots";
|
|
9
9
|
this.config = "none";
|
|
10
10
|
this.coverage = true;
|
|
11
|
+
this.env = {};
|
|
11
12
|
this.buildOptions = new BuildOptions();
|
|
12
13
|
this.runOptions = new RunOptions();
|
|
14
|
+
this.modes = {};
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
export class CoverageOptions {
|
|
@@ -25,6 +27,7 @@ export class Suite {
|
|
|
25
27
|
}
|
|
26
28
|
export class BuildOptions {
|
|
27
29
|
constructor() {
|
|
30
|
+
this.cmd = "";
|
|
28
31
|
this.args = [];
|
|
29
32
|
this.target = "wasi";
|
|
30
33
|
}
|
|
@@ -40,3 +43,18 @@ export class Runtime {
|
|
|
40
43
|
this.cmd = "node ./.as-test/runners/default.wasi.js <file>";
|
|
41
44
|
}
|
|
42
45
|
}
|
|
46
|
+
export class ModeConfig {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.buildOptions = {};
|
|
49
|
+
this.runOptions = {};
|
|
50
|
+
this.env = {};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class ReporterConfig {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.name = "";
|
|
56
|
+
this.options = [];
|
|
57
|
+
this.outDir = "";
|
|
58
|
+
this.outFile = "";
|
|
59
|
+
}
|
|
60
|
+
}
|