as-test 1.0.6 → 1.0.9
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 +13 -0
- package/README.md +45 -0
- package/as-test.config.schema.json +39 -0
- package/assembly/__fuzz__/string.fuzz.ts +1 -1
- package/assembly/as-test.intellisense.d.ts +60 -0
- package/assembly/index.ts +32 -3
- package/assembly/src/fuzz.ts +55 -14
- package/assembly/util/wipc.ts +14 -4
- package/bin/commands/fuzz-core.js +30 -2
- package/bin/commands/init-core.js +129 -19
- package/bin/commands/run-core.js +215 -21
- package/bin/commands/web-runner-source.js +86 -13
- package/bin/coverage-points.js +173 -0
- package/bin/index.js +62 -4
- package/bin/reporters/default.js +103 -4
- package/bin/types.js +9 -0
- package/bin/util.js +16 -0
- package/package.json +2 -1
|
@@ -396,6 +396,10 @@ function printPlan(root, target, example, fuzzExample, install) {
|
|
|
396
396
|
path: ".as-test/runners/default.bindings.js",
|
|
397
397
|
isDir: false,
|
|
398
398
|
});
|
|
399
|
+
fileEntries.push({
|
|
400
|
+
path: ".as-test/runners/default.bindings.hooks.js",
|
|
401
|
+
isDir: false,
|
|
402
|
+
});
|
|
399
403
|
fileEntries.push({
|
|
400
404
|
path: ".as-test/runners/default.wasi.js",
|
|
401
405
|
isDir: false,
|
|
@@ -404,6 +408,10 @@ function printPlan(root, target, example, fuzzExample, install) {
|
|
|
404
408
|
path: ".as-test/runners/default.web.js",
|
|
405
409
|
isDir: false,
|
|
406
410
|
});
|
|
411
|
+
fileEntries.push({
|
|
412
|
+
path: ".as-test/runners/default.web.hooks.js",
|
|
413
|
+
isDir: false,
|
|
414
|
+
});
|
|
407
415
|
}
|
|
408
416
|
if (example != "none") {
|
|
409
417
|
fileEntries.push({
|
|
@@ -514,10 +522,18 @@ function applyInit(root, target, example, fuzzExample, force) {
|
|
|
514
522
|
const runnerPath = path.join(root, ".as-test/runners/default.bindings.js");
|
|
515
523
|
writeManagedFile(runnerPath, buildBindingsRunner(), force, summary, ".as-test/runners/default.bindings.js");
|
|
516
524
|
}
|
|
525
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
526
|
+
const hooksPath = path.join(root, ".as-test/runners/default.bindings.hooks.js");
|
|
527
|
+
writeManagedFile(hooksPath, buildBindingsRunnerHooks(), force, summary, ".as-test/runners/default.bindings.hooks.js");
|
|
528
|
+
}
|
|
517
529
|
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
518
530
|
const runnerPath = path.join(root, ".as-test/runners/default.web.js");
|
|
519
531
|
writeManagedFile(runnerPath, buildWebRunnerSource(), force, summary, ".as-test/runners/default.web.js");
|
|
520
532
|
}
|
|
533
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
534
|
+
const hooksPath = path.join(root, ".as-test/runners/default.web.hooks.js");
|
|
535
|
+
writeManagedFile(hooksPath, buildWebRunnerHooks(), force, summary, ".as-test/runners/default.web.hooks.js");
|
|
536
|
+
}
|
|
521
537
|
const pkgPath = path.join(root, "package.json");
|
|
522
538
|
const pkg = existsSync(pkgPath)
|
|
523
539
|
? JSON.parse(readFileSync(pkgPath, "utf8"))
|
|
@@ -961,7 +977,7 @@ function buildBasicFuzzerExample() {
|
|
|
961
977
|
fuzz("basic string fuzzer", (value: string): bool => {
|
|
962
978
|
expect(value.length >= 0).toBe(true);
|
|
963
979
|
return value.length <= 24;
|
|
964
|
-
}).generate((seed: FuzzSeed, run: (value: string) => bool): void => {
|
|
980
|
+
}, 250).generate((seed: FuzzSeed, run: (value: string) => bool): void => {
|
|
965
981
|
run(
|
|
966
982
|
seed.string({
|
|
967
983
|
charset: "ascii",
|
|
@@ -1028,7 +1044,10 @@ function buildBindingsRunner() {
|
|
|
1028
1044
|
import path from "path";
|
|
1029
1045
|
import { pathToFileURL } from "url";
|
|
1030
1046
|
|
|
1031
|
-
|
|
1047
|
+
const HOOKS_PATH = path.resolve(
|
|
1048
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
1049
|
+
"./default.bindings.hooks.js",
|
|
1050
|
+
);
|
|
1032
1051
|
|
|
1033
1052
|
function readExact(length) {
|
|
1034
1053
|
const out = Buffer.alloc(length);
|
|
@@ -1055,20 +1074,76 @@ function writeRaw(data) {
|
|
|
1055
1074
|
fs.writeSync(1, view);
|
|
1056
1075
|
}
|
|
1057
1076
|
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1077
|
+
function createRunnerContext({ wasmPath, module, helperPath }) {
|
|
1078
|
+
return {
|
|
1079
|
+
wasmPath,
|
|
1080
|
+
helperPath,
|
|
1081
|
+
module,
|
|
1082
|
+
argv: process.argv.slice(2),
|
|
1083
|
+
env: process.env,
|
|
1084
|
+
readFrame(size) {
|
|
1085
|
+
return readExact(Number(size ?? 0));
|
|
1086
|
+
},
|
|
1087
|
+
writeFrame(data) {
|
|
1088
|
+
writeRaw(data);
|
|
1089
|
+
return true;
|
|
1090
|
+
},
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function createAsTestImports(ctx) {
|
|
1095
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
1096
|
+
process.stdout.write = (chunk, ...args) => {
|
|
1097
|
+
if (chunk instanceof ArrayBuffer) {
|
|
1098
|
+
return ctx.writeFrame(chunk);
|
|
1099
|
+
}
|
|
1100
|
+
return originalWrite(chunk, ...args);
|
|
1101
|
+
};
|
|
1102
|
+
process.stdin.read = (size) => ctx.readFrame(size);
|
|
1103
|
+
return {};
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function mergeImports(...groups) {
|
|
1107
|
+
const out = {};
|
|
1108
|
+
for (const group of groups) {
|
|
1109
|
+
if (!group || typeof group != "object") continue;
|
|
1110
|
+
for (const moduleName of Object.keys(group)) {
|
|
1111
|
+
out[moduleName] = Object.assign(out[moduleName] || {}, group[moduleName]);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return out;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function loadRunnerHooks() {
|
|
1118
|
+
if (!fs.existsSync(HOOKS_PATH)) {
|
|
1119
|
+
return {
|
|
1120
|
+
createUserImports() {
|
|
1121
|
+
return {};
|
|
1122
|
+
},
|
|
1123
|
+
async runModule(_exports, _ctx) {},
|
|
1068
1124
|
};
|
|
1069
|
-
process.stdin.read = (size) => readExact(Number(size ?? 0));
|
|
1070
1125
|
}
|
|
1071
|
-
|
|
1126
|
+
const mod = await import(pathToFileURL(HOOKS_PATH).href + "?t=" + Date.now());
|
|
1127
|
+
return {
|
|
1128
|
+
createUserImports:
|
|
1129
|
+
typeof mod.createUserImports == "function"
|
|
1130
|
+
? mod.createUserImports
|
|
1131
|
+
: () => ({}),
|
|
1132
|
+
runModule:
|
|
1133
|
+
typeof mod.runModule == "function" ? mod.runModule : async () => {},
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
async function instantiateModule(ctx, hooks) {
|
|
1138
|
+
const helper = await import(pathToFileURL(ctx.helperPath).href);
|
|
1139
|
+
if (typeof helper.instantiate !== "function") {
|
|
1140
|
+
throw new Error("bindings helper missing instantiate export");
|
|
1141
|
+
}
|
|
1142
|
+
const imports = mergeImports(
|
|
1143
|
+
createAsTestImports(ctx),
|
|
1144
|
+
await hooks.createUserImports(ctx),
|
|
1145
|
+
);
|
|
1146
|
+
return helper.instantiate(ctx.module, imports);
|
|
1072
1147
|
}
|
|
1073
1148
|
|
|
1074
1149
|
const wasmPathArg = process.argv[2];
|
|
@@ -1083,14 +1158,49 @@ const jsPath = wasmPath.replace(/\\.wasm$/, ".js");
|
|
|
1083
1158
|
try {
|
|
1084
1159
|
const binary = fs.readFileSync(wasmPath);
|
|
1085
1160
|
const module = new WebAssembly.Module(binary);
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
mod.instantiate(module, withNodeIo({}));
|
|
1161
|
+
const ctx = createRunnerContext({ wasmPath, module, helperPath: jsPath });
|
|
1162
|
+
const hooks = await loadRunnerHooks();
|
|
1163
|
+
const exports = await instantiateModule(ctx, hooks);
|
|
1164
|
+
await hooks.runModule(exports, ctx);
|
|
1091
1165
|
} catch (error) {
|
|
1092
1166
|
process.stderr.write("failed to run bindings module: " + String(error) + "\\n");
|
|
1093
1167
|
process.exit(1);
|
|
1094
1168
|
}
|
|
1095
1169
|
`;
|
|
1096
1170
|
}
|
|
1171
|
+
function buildBindingsRunnerHooks() {
|
|
1172
|
+
return `export function createUserImports(_ctx) {
|
|
1173
|
+
return {
|
|
1174
|
+
// env: {
|
|
1175
|
+
// now_ms: () => Date.now(),
|
|
1176
|
+
// },
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
export async function runModule(_exports, _ctx) {
|
|
1181
|
+
// The generated bindings helper already calls exports._start().
|
|
1182
|
+
// Add extra startup calls here when your module exposes them.
|
|
1183
|
+
//
|
|
1184
|
+
// Example:
|
|
1185
|
+
// _exports.run?.();
|
|
1186
|
+
}
|
|
1187
|
+
`;
|
|
1188
|
+
}
|
|
1189
|
+
function buildWebRunnerHooks() {
|
|
1190
|
+
return `export function createUserImports(_ctx) {
|
|
1191
|
+
return {
|
|
1192
|
+
// env: {
|
|
1193
|
+
// now_ms: () => performance.now(),
|
|
1194
|
+
// },
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
export async function runModule(_exports, _ctx) {
|
|
1199
|
+
// The generated bindings helper already calls exports._start().
|
|
1200
|
+
// Add extra startup calls here when your module exposes them.
|
|
1201
|
+
//
|
|
1202
|
+
// Example:
|
|
1203
|
+
// _exports.run?.();
|
|
1204
|
+
}
|
|
1205
|
+
`;
|
|
1206
|
+
}
|
package/bin/commands/run-core.js
CHANGED
|
@@ -6,10 +6,11 @@ import { applyMode, formatTime, getExec, loadConfig, tokenizeCommand, } from "..
|
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
import { pathToFileURL } from "url";
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
-
import { buildWebRunnerSource } from "./web-runner-source.js";
|
|
9
|
+
import { buildWebRunnerHooksSource, buildWebRunnerSource, } from "./web-runner-source.js";
|
|
10
10
|
import { createReporter as createDefaultReporter } from "../reporters/default.js";
|
|
11
11
|
import { createTapReporter } from "../reporters/tap.js";
|
|
12
12
|
import { persistCrashRecord } from "../crash-store.js";
|
|
13
|
+
import { describeCoveragePoint } from "../coverage-points.js";
|
|
13
14
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
14
15
|
class SnapshotStore {
|
|
15
16
|
constructor(specFile, snapshotDir, duplicateSpecBasenames = new Set()) {
|
|
@@ -578,9 +579,27 @@ function applyConfiguredFileTotalToStats(stats, fileSummaryTotal) {
|
|
|
578
579
|
stats.skippedFiles += unexecuted;
|
|
579
580
|
}
|
|
580
581
|
function resolveRuntimeCommand(runtimeRun, target, emitWarnings = true) {
|
|
581
|
-
const
|
|
582
|
+
const targetDefaultAligned = alignDefaultRuntimeToTarget(runtimeRun, target);
|
|
583
|
+
const normalized = resolveLegacyRuntime(targetDefaultAligned, target, emitWarnings);
|
|
582
584
|
return fallbackToDefaultRuntime(normalized, target, emitWarnings);
|
|
583
585
|
}
|
|
586
|
+
function alignDefaultRuntimeToTarget(runtimeRun, target) {
|
|
587
|
+
const fallback = getDefaultRuntimeFallback(target);
|
|
588
|
+
if (!fallback)
|
|
589
|
+
return runtimeRun;
|
|
590
|
+
const trimmed = runtimeRun.trim();
|
|
591
|
+
if (!trimmed.length || trimmed == fallback.command)
|
|
592
|
+
return runtimeRun;
|
|
593
|
+
const defaults = ["wasi", "bindings", "web"]
|
|
594
|
+
.map((kind) => getDefaultRuntimeFallback(kind))
|
|
595
|
+
.filter((item) => item != null);
|
|
596
|
+
for (const entry of defaults) {
|
|
597
|
+
if (entry.command != fallback.command && entry.command == trimmed) {
|
|
598
|
+
return fallback.command;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return runtimeRun;
|
|
602
|
+
}
|
|
584
603
|
function resolveLegacyRuntime(runtimeRun, target, emitWarnings) {
|
|
585
604
|
if (target == "wasi") {
|
|
586
605
|
const preferredPath = "./.as-test/runners/default.wasi.js";
|
|
@@ -681,8 +700,10 @@ function ensureDefaultRuntimeRunner(target, emitWarnings) {
|
|
|
681
700
|
if (!fallback)
|
|
682
701
|
return null;
|
|
683
702
|
const resolvedScriptPath = path.join(process.cwd(), fallback.scriptPath);
|
|
684
|
-
if (existsSync(resolvedScriptPath))
|
|
703
|
+
if (existsSync(resolvedScriptPath)) {
|
|
704
|
+
ensureDefaultRuntimeHookFiles(target);
|
|
685
705
|
return fallback;
|
|
706
|
+
}
|
|
686
707
|
const source = getDefaultRuntimeRunnerSource(target);
|
|
687
708
|
if (!source)
|
|
688
709
|
return fallback;
|
|
@@ -693,8 +714,39 @@ function ensureDefaultRuntimeRunner(target, emitWarnings) {
|
|
|
693
714
|
if (emitWarnings) {
|
|
694
715
|
process.stderr.write(chalk.dim(`runtime script missing; created ${fallback.scriptPath}\n`));
|
|
695
716
|
}
|
|
717
|
+
ensureDefaultRuntimeHookFiles(target);
|
|
696
718
|
return fallback;
|
|
697
719
|
}
|
|
720
|
+
function ensureDefaultRuntimeHookFiles(target) {
|
|
721
|
+
const hooks = getDefaultRuntimeRunnerHookFiles(target);
|
|
722
|
+
for (const file of hooks) {
|
|
723
|
+
if (existsSync(file.path))
|
|
724
|
+
continue;
|
|
725
|
+
if (!existsSync(path.dirname(file.path))) {
|
|
726
|
+
mkdirSync(path.dirname(file.path), { recursive: true });
|
|
727
|
+
}
|
|
728
|
+
writeFileSync(file.path, file.source);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function getDefaultRuntimeRunnerHookFiles(target) {
|
|
732
|
+
if (target == "bindings") {
|
|
733
|
+
return [
|
|
734
|
+
{
|
|
735
|
+
path: path.join(process.cwd(), "./.as-test/runners/default.bindings.hooks.js"),
|
|
736
|
+
source: getDefaultBindingsRunnerHooksSource(),
|
|
737
|
+
},
|
|
738
|
+
];
|
|
739
|
+
}
|
|
740
|
+
if (target == "web") {
|
|
741
|
+
return [
|
|
742
|
+
{
|
|
743
|
+
path: path.join(process.cwd(), "./.as-test/runners/default.web.hooks.js"),
|
|
744
|
+
source: buildWebRunnerHooksSource(),
|
|
745
|
+
},
|
|
746
|
+
];
|
|
747
|
+
}
|
|
748
|
+
return [];
|
|
749
|
+
}
|
|
698
750
|
function getDefaultRuntimeRunnerSource(target) {
|
|
699
751
|
if (target == "wasi") {
|
|
700
752
|
return `import { readFileSync } from "fs";
|
|
@@ -757,7 +809,10 @@ try {
|
|
|
757
809
|
import path from "path";
|
|
758
810
|
import { pathToFileURL } from "url";
|
|
759
811
|
|
|
760
|
-
|
|
812
|
+
const HOOKS_PATH = path.resolve(
|
|
813
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
814
|
+
"./default.bindings.hooks.js",
|
|
815
|
+
);
|
|
761
816
|
|
|
762
817
|
function readExact(length) {
|
|
763
818
|
const out = Buffer.alloc(length);
|
|
@@ -784,20 +839,76 @@ function writeRaw(data) {
|
|
|
784
839
|
fs.writeSync(1, view);
|
|
785
840
|
}
|
|
786
841
|
|
|
787
|
-
function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
842
|
+
function createRunnerContext({ wasmPath, module, helperPath }) {
|
|
843
|
+
return {
|
|
844
|
+
wasmPath,
|
|
845
|
+
helperPath,
|
|
846
|
+
module,
|
|
847
|
+
argv: process.argv.slice(2),
|
|
848
|
+
env: process.env,
|
|
849
|
+
readFrame(size) {
|
|
850
|
+
return readExact(Number(size ?? 0));
|
|
851
|
+
},
|
|
852
|
+
writeFrame(data) {
|
|
853
|
+
writeRaw(data);
|
|
854
|
+
return true;
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function createAsTestImports(ctx) {
|
|
860
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
861
|
+
process.stdout.write = (chunk, ...args) => {
|
|
862
|
+
if (chunk instanceof ArrayBuffer) {
|
|
863
|
+
return ctx.writeFrame(chunk);
|
|
864
|
+
}
|
|
865
|
+
return originalWrite(chunk, ...args);
|
|
866
|
+
};
|
|
867
|
+
process.stdin.read = (size) => ctx.readFrame(size);
|
|
868
|
+
return {};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function mergeImports(...groups) {
|
|
872
|
+
const out = {};
|
|
873
|
+
for (const group of groups) {
|
|
874
|
+
if (!group || typeof group != "object") continue;
|
|
875
|
+
for (const moduleName of Object.keys(group)) {
|
|
876
|
+
out[moduleName] = Object.assign(out[moduleName] || {}, group[moduleName]);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return out;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
async function loadRunnerHooks() {
|
|
883
|
+
if (!fs.existsSync(HOOKS_PATH)) {
|
|
884
|
+
return {
|
|
885
|
+
createUserImports() {
|
|
886
|
+
return {};
|
|
887
|
+
},
|
|
888
|
+
async runModule(_exports, _ctx) {},
|
|
797
889
|
};
|
|
798
|
-
process.stdin.read = (size) => readExact(Number(size ?? 0));
|
|
799
890
|
}
|
|
800
|
-
|
|
891
|
+
const mod = await import(pathToFileURL(HOOKS_PATH).href + "?t=" + Date.now());
|
|
892
|
+
return {
|
|
893
|
+
createUserImports:
|
|
894
|
+
typeof mod.createUserImports == "function"
|
|
895
|
+
? mod.createUserImports
|
|
896
|
+
: () => ({}),
|
|
897
|
+
runModule:
|
|
898
|
+
typeof mod.runModule == "function" ? mod.runModule : async () => {},
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
async function instantiateModule(ctx, hooks) {
|
|
903
|
+
const helper = await import(pathToFileURL(ctx.helperPath).href);
|
|
904
|
+
if (typeof helper.instantiate !== "function") {
|
|
905
|
+
throw new Error("bindings helper missing instantiate export");
|
|
906
|
+
}
|
|
907
|
+
const imports = mergeImports(
|
|
908
|
+
createAsTestImports(ctx),
|
|
909
|
+
await hooks.createUserImports(ctx),
|
|
910
|
+
);
|
|
911
|
+
return helper.instantiate(ctx.module, imports);
|
|
801
912
|
}
|
|
802
913
|
|
|
803
914
|
const wasmPathArg = process.argv[2];
|
|
@@ -812,11 +923,10 @@ const jsPath = wasmPath.replace(/\\.wasm$/, ".js");
|
|
|
812
923
|
try {
|
|
813
924
|
const binary = fs.readFileSync(wasmPath);
|
|
814
925
|
const module = new WebAssembly.Module(binary);
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
mod.instantiate(module, withNodeIo({}));
|
|
926
|
+
const ctx = createRunnerContext({ wasmPath, module, helperPath: jsPath });
|
|
927
|
+
const hooks = await loadRunnerHooks();
|
|
928
|
+
const exports = await instantiateModule(ctx, hooks);
|
|
929
|
+
await hooks.runModule(exports, ctx);
|
|
820
930
|
} catch (error) {
|
|
821
931
|
process.stderr.write("failed to run bindings module: " + String(error) + "\\n");
|
|
822
932
|
process.exit(1);
|
|
@@ -828,6 +938,24 @@ try {
|
|
|
828
938
|
}
|
|
829
939
|
return null;
|
|
830
940
|
}
|
|
941
|
+
function getDefaultBindingsRunnerHooksSource() {
|
|
942
|
+
return `export function createUserImports(_ctx) {
|
|
943
|
+
return {
|
|
944
|
+
// env: {
|
|
945
|
+
// now_ms: () => Date.now(),
|
|
946
|
+
// },
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export async function runModule(_exports, _ctx) {
|
|
951
|
+
// The generated bindings helper already calls exports._start().
|
|
952
|
+
// Add extra startup calls here when your module exposes them.
|
|
953
|
+
//
|
|
954
|
+
// Example:
|
|
955
|
+
// _exports.run?.();
|
|
956
|
+
}
|
|
957
|
+
`;
|
|
958
|
+
}
|
|
831
959
|
function resolveArtifactFileName(file, target, modeName, duplicateSpecBasenames = new Set()) {
|
|
832
960
|
const base = path
|
|
833
961
|
.basename(file)
|
|
@@ -1091,6 +1219,8 @@ function collectCoverageSummary(reports, enabled, showPoints, coverage) {
|
|
|
1091
1219
|
for (const point of report.coverage.points) {
|
|
1092
1220
|
if (isIgnoredCoverageFile(point.file, coverage))
|
|
1093
1221
|
continue;
|
|
1222
|
+
if (isIgnoredCoveragePoint(point, coverage))
|
|
1223
|
+
continue;
|
|
1094
1224
|
const key = `${point.file}::${point.hash}`;
|
|
1095
1225
|
const existing = uniquePoints.get(key);
|
|
1096
1226
|
if (!existing) {
|
|
@@ -1246,10 +1376,19 @@ function resolveCoverageOptions(raw) {
|
|
|
1246
1376
|
includeSpecs: false,
|
|
1247
1377
|
include: [],
|
|
1248
1378
|
exclude: [],
|
|
1379
|
+
ignore: {
|
|
1380
|
+
labels: [],
|
|
1381
|
+
names: [],
|
|
1382
|
+
locations: [],
|
|
1383
|
+
snippets: [],
|
|
1384
|
+
},
|
|
1249
1385
|
};
|
|
1250
1386
|
}
|
|
1251
1387
|
if (raw && typeof raw == "object") {
|
|
1252
1388
|
const obj = raw;
|
|
1389
|
+
const ignore = obj.ignore && typeof obj.ignore == "object" && !Array.isArray(obj.ignore)
|
|
1390
|
+
? obj.ignore
|
|
1391
|
+
: null;
|
|
1253
1392
|
return {
|
|
1254
1393
|
enabled: obj.enabled == null ? false : Boolean(obj.enabled),
|
|
1255
1394
|
includeSpecs: Boolean(obj.includeSpecs),
|
|
@@ -1259,6 +1398,20 @@ function resolveCoverageOptions(raw) {
|
|
|
1259
1398
|
exclude: Array.isArray(obj.exclude)
|
|
1260
1399
|
? obj.exclude.filter((item) => typeof item == "string")
|
|
1261
1400
|
: [],
|
|
1401
|
+
ignore: {
|
|
1402
|
+
labels: Array.isArray(ignore?.labels)
|
|
1403
|
+
? ignore.labels.filter((item) => typeof item == "string")
|
|
1404
|
+
: [],
|
|
1405
|
+
names: Array.isArray(ignore?.names)
|
|
1406
|
+
? ignore.names.filter((item) => typeof item == "string")
|
|
1407
|
+
: [],
|
|
1408
|
+
locations: Array.isArray(ignore?.locations)
|
|
1409
|
+
? ignore.locations.filter((item) => typeof item == "string")
|
|
1410
|
+
: [],
|
|
1411
|
+
snippets: Array.isArray(ignore?.snippets)
|
|
1412
|
+
? ignore.snippets.filter((item) => typeof item == "string")
|
|
1413
|
+
: [],
|
|
1414
|
+
},
|
|
1262
1415
|
};
|
|
1263
1416
|
}
|
|
1264
1417
|
return {
|
|
@@ -1266,8 +1419,49 @@ function resolveCoverageOptions(raw) {
|
|
|
1266
1419
|
includeSpecs: false,
|
|
1267
1420
|
include: [],
|
|
1268
1421
|
exclude: [],
|
|
1422
|
+
ignore: {
|
|
1423
|
+
labels: [],
|
|
1424
|
+
names: [],
|
|
1425
|
+
locations: [],
|
|
1426
|
+
snippets: [],
|
|
1427
|
+
},
|
|
1269
1428
|
};
|
|
1270
1429
|
}
|
|
1430
|
+
function isIgnoredCoveragePoint(point, coverage) {
|
|
1431
|
+
const ignore = coverage.ignore;
|
|
1432
|
+
if (!ignore.labels.length &&
|
|
1433
|
+
!ignore.names.length &&
|
|
1434
|
+
!ignore.locations.length &&
|
|
1435
|
+
!ignore.snippets.length) {
|
|
1436
|
+
return false;
|
|
1437
|
+
}
|
|
1438
|
+
const info = describeCoveragePoint(point.file, point.line, point.column, point.type);
|
|
1439
|
+
const location = `${point.file.replace(/\\/g, "/")}:${point.line}:${point.column}`;
|
|
1440
|
+
const label = info.displayType.toLowerCase();
|
|
1441
|
+
const name = info.subjectName?.toLowerCase() ?? "";
|
|
1442
|
+
const snippet = info.visible.toLowerCase();
|
|
1443
|
+
if (ignore.labels.some((pattern) => matchesCoverageTextPattern(label, pattern.toLowerCase()))) {
|
|
1444
|
+
return true;
|
|
1445
|
+
}
|
|
1446
|
+
if (name.length &&
|
|
1447
|
+
ignore.names.some((pattern) => matchesCoverageTextPattern(name, pattern.toLowerCase()))) {
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
if (ignore.locations.some((pattern) => matchesCoverageTextPattern(location, pattern.replace(/\\/g, "/")))) {
|
|
1451
|
+
return true;
|
|
1452
|
+
}
|
|
1453
|
+
if (snippet.length &&
|
|
1454
|
+
ignore.snippets.some((pattern) => matchesCoverageTextPattern(snippet, pattern.toLowerCase()))) {
|
|
1455
|
+
return true;
|
|
1456
|
+
}
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
function matchesCoverageTextPattern(value, pattern) {
|
|
1460
|
+
const normalized = pattern.trim();
|
|
1461
|
+
if (!normalized.length)
|
|
1462
|
+
return false;
|
|
1463
|
+
return globPatternToRegExp(normalized).test(value);
|
|
1464
|
+
}
|
|
1271
1465
|
function compareCoveragePoints(a, b) {
|
|
1272
1466
|
if (a.line !== b.line)
|
|
1273
1467
|
return a.line - b.line;
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
export function buildWebRunnerHooksSource() {
|
|
2
|
+
return `export function createUserImports(_ctx) {
|
|
3
|
+
return {
|
|
4
|
+
// env: {
|
|
5
|
+
// now_ms: () => performance.now(),
|
|
6
|
+
// },
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function runModule(_exports, _ctx) {
|
|
11
|
+
// The generated bindings helper already calls exports._start().
|
|
12
|
+
// Add extra startup calls here when your module exposes them.
|
|
13
|
+
//
|
|
14
|
+
// Example:
|
|
15
|
+
// _exports.run?.();
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
1
19
|
export function buildWebRunnerSource() {
|
|
2
20
|
const html = String.raw `<!doctype html>
|
|
3
21
|
<html lang="en">
|
|
@@ -135,6 +153,7 @@ ws.addEventListener("open", () => {
|
|
|
135
153
|
worker.postMessage({
|
|
136
154
|
kind: "init",
|
|
137
155
|
helperUrl: "/artifact.js",
|
|
156
|
+
hooksUrl: "/runner-hooks.js",
|
|
138
157
|
wasmUrl: "/artifact.wasm",
|
|
139
158
|
replyBuffer,
|
|
140
159
|
});
|
|
@@ -165,32 +184,61 @@ ws.addEventListener("error", () => {
|
|
|
165
184
|
const worker = String.raw `let replyState = null;
|
|
166
185
|
let replyBytes = null;
|
|
167
186
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
187
|
+
function createRunnerContext({ helperUrl, wasmUrl, module }) {
|
|
188
|
+
return {
|
|
189
|
+
helperUrl,
|
|
190
|
+
wasmUrl,
|
|
191
|
+
module,
|
|
192
|
+
postFrame(frame) {
|
|
193
|
+
self.postMessage({ kind: "wipc", frame }, [frame]);
|
|
194
|
+
return true;
|
|
195
|
+
},
|
|
196
|
+
readFrame(size) {
|
|
197
|
+
return readReply(Number(size ?? 0));
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
177
201
|
|
|
202
|
+
function createAsTestImports(ctx) {
|
|
178
203
|
globalThis.process = {
|
|
179
204
|
stdout: {
|
|
180
205
|
write(data) {
|
|
181
206
|
const frame = data instanceof ArrayBuffer ? data : data?.buffer;
|
|
182
|
-
|
|
183
|
-
return true;
|
|
207
|
+
return ctx.postFrame(frame);
|
|
184
208
|
},
|
|
185
209
|
},
|
|
186
210
|
stdin: {
|
|
187
211
|
read(size) {
|
|
188
|
-
return
|
|
212
|
+
return ctx.readFrame(size);
|
|
189
213
|
},
|
|
190
214
|
},
|
|
191
215
|
};
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function mergeImports(...groups) {
|
|
220
|
+
const out = {};
|
|
221
|
+
for (const group of groups) {
|
|
222
|
+
if (!group || typeof group != "object") continue;
|
|
223
|
+
for (const moduleName of Object.keys(group)) {
|
|
224
|
+
out[moduleName] = Object.assign(out[moduleName] || {}, group[moduleName]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
self.onmessage = async (event) => {
|
|
231
|
+
const message = event.data ?? {};
|
|
232
|
+
if (message.kind != "init") {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const shared = message.replyBuffer;
|
|
237
|
+
replyState = new Int32Array(shared, 0, 2);
|
|
238
|
+
replyBytes = new Uint8Array(shared, 8);
|
|
192
239
|
|
|
193
240
|
try {
|
|
241
|
+
const hooks = await import(message.hooksUrl);
|
|
194
242
|
const helper = await import(message.helperUrl);
|
|
195
243
|
if (typeof helper.instantiate != "function") {
|
|
196
244
|
throw new Error("bindings helper missing instantiate export");
|
|
@@ -201,8 +249,22 @@ self.onmessage = async (event) => {
|
|
|
201
249
|
}
|
|
202
250
|
const binary = await response.arrayBuffer();
|
|
203
251
|
const module = new WebAssembly.Module(binary);
|
|
252
|
+
const ctx = createRunnerContext({
|
|
253
|
+
helperUrl: message.helperUrl,
|
|
254
|
+
wasmUrl: message.wasmUrl,
|
|
255
|
+
module,
|
|
256
|
+
});
|
|
257
|
+
const imports = mergeImports(
|
|
258
|
+
createAsTestImports(ctx),
|
|
259
|
+
typeof hooks.createUserImports == "function"
|
|
260
|
+
? await hooks.createUserImports(ctx)
|
|
261
|
+
: {},
|
|
262
|
+
);
|
|
204
263
|
self.postMessage({ kind: "ready" });
|
|
205
|
-
await helper.instantiate(module,
|
|
264
|
+
const exports = await helper.instantiate(module, imports);
|
|
265
|
+
if (typeof hooks.runModule == "function") {
|
|
266
|
+
await hooks.runModule(exports, ctx);
|
|
267
|
+
}
|
|
206
268
|
self.postMessage({ kind: "done" });
|
|
207
269
|
} catch (error) {
|
|
208
270
|
const message =
|
|
@@ -235,6 +297,7 @@ function readReply(max) {
|
|
|
235
297
|
return out.buffer;
|
|
236
298
|
}
|
|
237
299
|
`;
|
|
300
|
+
const hooks = buildWebRunnerHooksSource();
|
|
238
301
|
return `import { createHash } from "crypto";
|
|
239
302
|
import { existsSync, readFileSync } from "fs";
|
|
240
303
|
import http from "http";
|
|
@@ -244,6 +307,7 @@ import { spawn } from "child_process";
|
|
|
244
307
|
const INDEX_HTML = ${JSON.stringify(html)};
|
|
245
308
|
const CLIENT_JS = ${JSON.stringify(client)};
|
|
246
309
|
const WORKER_JS = ${JSON.stringify(worker)};
|
|
310
|
+
const DEFAULT_HOOKS_JS = ${JSON.stringify(hooks)};
|
|
247
311
|
const MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
248
312
|
const HEADLESS_FLAGS = [
|
|
249
313
|
"--headless=new",
|
|
@@ -264,6 +328,7 @@ if (!wasmArg) {
|
|
|
264
328
|
|
|
265
329
|
const wasmPath = path.resolve(process.cwd(), wasmArg);
|
|
266
330
|
const helperPath = wasmPath.replace(/\\.wasm$/, ".js");
|
|
331
|
+
const hooksPath = path.resolve(process.cwd(), ".as-test/runners/default.web.hooks.js");
|
|
267
332
|
if (!existsSync(wasmPath)) {
|
|
268
333
|
process.stderr.write("missing wasm artifact: " + wasmPath + "\\n");
|
|
269
334
|
process.exit(1);
|
|
@@ -321,6 +386,14 @@ const server = http.createServer((req, res) => {
|
|
|
321
386
|
res.end(readFileSync(helperPath, "utf8"));
|
|
322
387
|
return;
|
|
323
388
|
}
|
|
389
|
+
if (url == "/runner-hooks.js") {
|
|
390
|
+
res.writeHead(200, {
|
|
391
|
+
...headers,
|
|
392
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
393
|
+
});
|
|
394
|
+
res.end(existsSync(hooksPath) ? readFileSync(hooksPath, "utf8") : DEFAULT_HOOKS_JS);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
324
397
|
if (url == "/artifact.wasm") {
|
|
325
398
|
res.writeHead(200, {
|
|
326
399
|
...headers,
|