vitest-pool-assemblyscript 0.2.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/BINARYEN_VERSION +1 -0
- package/LICENSE +53 -0
- package/README.md +607 -0
- package/assembly/compare.ts +219 -0
- package/assembly/describe.ts +104 -0
- package/assembly/expect.ts +335 -0
- package/assembly/index.ts +14 -0
- package/assembly/options.ts +198 -0
- package/assembly/test.ts +147 -0
- package/assembly/tsconfig.json +6 -0
- package/binding.gyp +62 -0
- package/dist/ast-visitor-DC3SuTzs.mjs +310 -0
- package/dist/ast-visitor-DC3SuTzs.mjs.map +1 -0
- package/dist/compile-runner-8h0dBwG2.mjs +80 -0
- package/dist/compile-runner-8h0dBwG2.mjs.map +1 -0
- package/dist/compiler/transforms/strip-inline.d.mts +18 -0
- package/dist/compiler/transforms/strip-inline.d.mts.map +1 -0
- package/dist/compiler/transforms/strip-inline.mjs +38 -0
- package/dist/compiler/transforms/strip-inline.mjs.map +1 -0
- package/dist/compiler-CN6BRK_N.mjs +295 -0
- package/dist/compiler-CN6BRK_N.mjs.map +1 -0
- package/dist/config/index-v3.d.mts +111 -0
- package/dist/config/index-v3.d.mts.map +1 -0
- package/dist/config/index-v3.mjs +11 -0
- package/dist/config/index-v3.mjs.map +1 -0
- package/dist/config/index.d.mts +4 -0
- package/dist/config/index.mjs +8 -0
- package/dist/constants-CA50WBdr.mjs +130 -0
- package/dist/constants-CA50WBdr.mjs.map +1 -0
- package/dist/coverage-merge-0WqdC-dq.mjs +22 -0
- package/dist/coverage-merge-0WqdC-dq.mjs.map +1 -0
- package/dist/coverage-provider/index.d.mts +15 -0
- package/dist/coverage-provider/index.d.mts.map +1 -0
- package/dist/coverage-provider/index.mjs +535 -0
- package/dist/coverage-provider/index.mjs.map +1 -0
- package/dist/custom-provider-options-CF5C1kXb.d.mts +26 -0
- package/dist/custom-provider-options-CF5C1kXb.d.mts.map +1 -0
- package/dist/debug-IeEHsxy0.mjs +195 -0
- package/dist/debug-IeEHsxy0.mjs.map +1 -0
- package/dist/index-internal.d.mts +23 -0
- package/dist/index-internal.d.mts.map +1 -0
- package/dist/index-internal.mjs +4 -0
- package/dist/index-v3.d.mts +7 -0
- package/dist/index-v3.d.mts.map +1 -0
- package/dist/index-v3.mjs +206 -0
- package/dist/index-v3.mjs.map +1 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +8 -0
- package/dist/load-user-imports-J9eaAW0_.mjs +801 -0
- package/dist/load-user-imports-J9eaAW0_.mjs.map +1 -0
- package/dist/pool-runner-init-CEwLyNI3.d.mts +8 -0
- package/dist/pool-runner-init-CEwLyNI3.d.mts.map +1 -0
- package/dist/pool-runner-init-d5qScS41.mjs +400 -0
- package/dist/pool-runner-init-d5qScS41.mjs.map +1 -0
- package/dist/pool-thread/compile-worker-thread.d.mts +7 -0
- package/dist/pool-thread/compile-worker-thread.d.mts.map +1 -0
- package/dist/pool-thread/compile-worker-thread.mjs +42 -0
- package/dist/pool-thread/compile-worker-thread.mjs.map +1 -0
- package/dist/pool-thread/test-worker-thread.d.mts +7 -0
- package/dist/pool-thread/test-worker-thread.d.mts.map +1 -0
- package/dist/pool-thread/test-worker-thread.mjs +39 -0
- package/dist/pool-thread/test-worker-thread.mjs.map +1 -0
- package/dist/pool-thread/v3-tinypool-thread.d.mts +7 -0
- package/dist/pool-thread/v3-tinypool-thread.d.mts.map +1 -0
- package/dist/pool-thread/v3-tinypool-thread.mjs +57 -0
- package/dist/pool-thread/v3-tinypool-thread.mjs.map +1 -0
- package/dist/resolve-config-as1w-Qyz.mjs +65 -0
- package/dist/resolve-config-as1w-Qyz.mjs.map +1 -0
- package/dist/test-runner-B2BpyPNK.mjs +142 -0
- package/dist/test-runner-B2BpyPNK.mjs.map +1 -0
- package/dist/types-8KKo9Hbf.d.mts +228 -0
- package/dist/types-8KKo9Hbf.d.mts.map +1 -0
- package/dist/vitest-file-tasks-BUwzh375.mjs +61 -0
- package/dist/vitest-file-tasks-BUwzh375.mjs.map +1 -0
- package/dist/vitest-tasks-BKS7689f.mjs +319 -0
- package/dist/vitest-tasks-BKS7689f.mjs.map +1 -0
- package/dist/worker-rpc-channel-lbhK7Qz8.mjs +25 -0
- package/dist/worker-rpc-channel-lbhK7Qz8.mjs.map +1 -0
- package/package.json +112 -0
- package/prebuilds/linux-x64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/scripts/install.js +91 -0
- package/scripts/setup-binaryen.js +179 -0
- package/src/native-instrumentation/addon.cpp +788 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
import { COVERAGE_PAYLOAD_FORMATS, POOL_ERROR_NAMES, POOL_INTERNAL_PATHS, TEST_ERROR_NAMES } from "./constants-CA50WBdr.mjs";
|
|
2
|
+
import { createPoolError, createPoolErrorFromAnyError, debug, getSourceCodeFrameString, isDebugModeEnabled, toPlaintextStackFrameString, toVitestLikeStackFrameString } from "./debug-IeEHsxy0.mjs";
|
|
3
|
+
import { createAfterSuiteRunMeta, createSuiteTask, createTestTask, createWebAssemblyCallSite, extractCallStack, failTest, getTaskLogLabel, isSuiteOwnFile, parseSourceMap } from "./vitest-tasks-BKS7689f.mjs";
|
|
4
|
+
import { basename, resolve } from "node:path";
|
|
5
|
+
import { createBirpc } from "birpc";
|
|
6
|
+
import { diff } from "@vitest/utils/diff";
|
|
7
|
+
import { SourceMapConsumer } from "source-map";
|
|
8
|
+
|
|
9
|
+
//#region src/util/assemblyscript/binding-helpers.ts
|
|
10
|
+
const STRING_EXTRACT_CHUNK_SIZE = 1024;
|
|
11
|
+
/**
|
|
12
|
+
* Decode an AssemblyScript string from WASM memory, using the length stored at
|
|
13
|
+
* the beginning of the string.
|
|
14
|
+
*
|
|
15
|
+
* This approach is borrowed from AssemblyScript with changes for clarity.
|
|
16
|
+
* AssemblyScript is released under the Apache 2.0 license, included here.
|
|
17
|
+
*
|
|
18
|
+
* When a string argument crosses the boundary to JS, we get a pointer to the
|
|
19
|
+
* string data in WASM memory. WASM strings store their length in the 4 bytes
|
|
20
|
+
* before the string data pointer so we know how much to read from memory.
|
|
21
|
+
*
|
|
22
|
+
* @param memory - WebAssembly memory instance
|
|
23
|
+
* @param pointer - Pointer to the start of the string
|
|
24
|
+
* @returns Decoded string
|
|
25
|
+
*/
|
|
26
|
+
function liftString(memory, pointer) {
|
|
27
|
+
if (!pointer) return void 0;
|
|
28
|
+
const unsigned = pointer >>> 0;
|
|
29
|
+
const uint32LengthPtr = unsigned - 4 >>> 2;
|
|
30
|
+
const byteOffsetLength = new Uint32Array(memory.buffer)[uint32LengthPtr];
|
|
31
|
+
if (byteOffsetLength === 0) return "";
|
|
32
|
+
const uint16EndPtr = unsigned + byteOffsetLength >>> 1;
|
|
33
|
+
let uint16StartPtr = unsigned >>> 1;
|
|
34
|
+
const memoryU16 = new Uint16Array(memory.buffer);
|
|
35
|
+
let string = "";
|
|
36
|
+
while (uint16EndPtr - uint16StartPtr > STRING_EXTRACT_CHUNK_SIZE) string += String.fromCharCode(...memoryU16.subarray(uint16StartPtr, uint16StartPtr += STRING_EXTRACT_CHUNK_SIZE));
|
|
37
|
+
return string + String.fromCharCode(...memoryU16.subarray(uint16StartPtr, uint16EndPtr));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/wasm-executor/wasm-memory.ts
|
|
42
|
+
/**
|
|
43
|
+
* Create a WebAssembly memory instance
|
|
44
|
+
* Used for imported memory pattern (matches --importMemory flag)
|
|
45
|
+
*/
|
|
46
|
+
function createMemory(initialPages, maximumPages) {
|
|
47
|
+
return new WebAssembly.Memory({
|
|
48
|
+
initial: initialPages,
|
|
49
|
+
maximum: maximumPages
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Decode AssemblyScript abort information
|
|
54
|
+
*
|
|
55
|
+
* Helper for handling abort() calls from AssemblyScript runtime.
|
|
56
|
+
* Decodes the error message and file path from WASM memory.
|
|
57
|
+
*
|
|
58
|
+
* @param memory - WebAssembly memory instance
|
|
59
|
+
* @param msgPtr - Pointer to error message string (or 0 if none)
|
|
60
|
+
* @param filePtr - Pointer to file path string (or 0 if none)
|
|
61
|
+
* @param line - Line number where abort occurred
|
|
62
|
+
* @param column - Column number where abort occurred
|
|
63
|
+
* @returns Decoded message and location (null if no meaningful location info)
|
|
64
|
+
*/
|
|
65
|
+
function decodeAbortInfo(memory, msgPtr, filePtr, line, column) {
|
|
66
|
+
const errorMsg = liftString(memory, msgPtr) ?? "Unknown error";
|
|
67
|
+
const filePath = liftString(memory, filePtr);
|
|
68
|
+
return {
|
|
69
|
+
message: errorMsg,
|
|
70
|
+
location: filePath && filePath !== "unknown" && (line !== 0 || column !== 0) ? `${filePath}:${line}:${column}` : null
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/wasm-executor/wasm-console.ts
|
|
76
|
+
function createWasmConsole(memory, handleLog) {
|
|
77
|
+
const getMessage = (msgPtr, memory, prefix = "") => {
|
|
78
|
+
return `${prefix}${msgPtr ? liftString(memory, msgPtr) : "<no message>"}`;
|
|
79
|
+
};
|
|
80
|
+
const timersByLabel = {};
|
|
81
|
+
return {
|
|
82
|
+
"console.assert": (assertion, msgPtr) => {
|
|
83
|
+
if (!assertion) {
|
|
84
|
+
const msg = getMessage(msgPtr, memory);
|
|
85
|
+
handleLog(`Assertion failed${msg ? `: ${msg}` : ""}`);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"console.log": (msgPtr) => {
|
|
89
|
+
handleLog(getMessage(msgPtr, memory));
|
|
90
|
+
},
|
|
91
|
+
"console.debug": (msgPtr) => {
|
|
92
|
+
handleLog(getMessage(msgPtr, memory, "Debug: "));
|
|
93
|
+
},
|
|
94
|
+
"console.info": (msgPtr) => {
|
|
95
|
+
handleLog(getMessage(msgPtr, memory, "Info: "));
|
|
96
|
+
},
|
|
97
|
+
"console.warn": (msgPtr) => {
|
|
98
|
+
handleLog(getMessage(msgPtr, memory, "Warning: "), true);
|
|
99
|
+
},
|
|
100
|
+
"console.error": (msgPtr) => {
|
|
101
|
+
handleLog(getMessage(msgPtr, memory, "Error: "), true);
|
|
102
|
+
},
|
|
103
|
+
"console.time": (labelPtr) => {
|
|
104
|
+
const label = labelPtr ? liftString(memory, labelPtr) ?? "default" : "default";
|
|
105
|
+
timersByLabel[label] = performance.now();
|
|
106
|
+
},
|
|
107
|
+
"console.timeLog": (labelPtr) => {
|
|
108
|
+
const label = labelPtr ? liftString(memory, labelPtr) ?? "default" : "default";
|
|
109
|
+
const start = timersByLabel[label];
|
|
110
|
+
let msg = "";
|
|
111
|
+
if (start === void 0) msg = `Warning: No such label '${label}' for console.timeLog()`;
|
|
112
|
+
else msg = `${label}: ${(performance.now() - start).toFixed(3)}ms`;
|
|
113
|
+
handleLog(msg);
|
|
114
|
+
},
|
|
115
|
+
"console.timeEnd": (labelPtr) => {
|
|
116
|
+
const label = labelPtr ? liftString(memory, labelPtr) ?? "default" : "default";
|
|
117
|
+
const start = timersByLabel[label];
|
|
118
|
+
let msg = "";
|
|
119
|
+
if (start === void 0) msg = `Warning: No such label '${label}' for console.timeLog()`;
|
|
120
|
+
else msg = `${label}: ${(performance.now() - start).toFixed(3)}ms`;
|
|
121
|
+
handleLog(msg);
|
|
122
|
+
delete timersByLabel[label];
|
|
123
|
+
},
|
|
124
|
+
trace(msgPtr, n, a0, a1, a2, a3) {
|
|
125
|
+
const msg = liftString(memory, msgPtr);
|
|
126
|
+
const args = [
|
|
127
|
+
a0,
|
|
128
|
+
a1,
|
|
129
|
+
a2,
|
|
130
|
+
a3
|
|
131
|
+
];
|
|
132
|
+
const nArgs = n && n > 0 ? args.slice(0, n) : args;
|
|
133
|
+
console.trace(`WASM Trace:${msg ? ` ${msg}` : ""}`, ...nArgs);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/wasm-executor/collect-options.ts
|
|
140
|
+
const TEST_OPTION_UNDEFINED = -1;
|
|
141
|
+
const TEST_OPTION_TRUE = 1;
|
|
142
|
+
function mergeAssemblyScriptTestOptions(baseOptions, timeout, retry, skip, only, fails) {
|
|
143
|
+
const options = { ...baseOptions };
|
|
144
|
+
if (timeout > TEST_OPTION_UNDEFINED) options.timeout = timeout;
|
|
145
|
+
if (retry > TEST_OPTION_UNDEFINED) options.retry = retry;
|
|
146
|
+
if (skip > TEST_OPTION_UNDEFINED) options.skip = skip === TEST_OPTION_TRUE ? true : false;
|
|
147
|
+
if (only > TEST_OPTION_UNDEFINED) options.only = only === TEST_OPTION_TRUE ? true : false;
|
|
148
|
+
if (fails > TEST_OPTION_UNDEFINED) options.fails = fails === TEST_OPTION_TRUE ? true : false;
|
|
149
|
+
return options;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/wasm-executor/wasm-imports.ts
|
|
154
|
+
function createUserWasmImports(createWasmImports, memory, module, logPrefix) {
|
|
155
|
+
let userEnvImports;
|
|
156
|
+
let userCustomEnvImports;
|
|
157
|
+
if (createWasmImports) try {
|
|
158
|
+
const start = performance.now();
|
|
159
|
+
const userImports = createWasmImports({
|
|
160
|
+
memory,
|
|
161
|
+
module,
|
|
162
|
+
utils: { liftString: (stringPtr) => liftString(memory, stringPtr) }
|
|
163
|
+
});
|
|
164
|
+
debug(`${logPrefix} Created user WASM imports for test execution in ${(performance.now() - start).toFixed(2)} ms`);
|
|
165
|
+
userEnvImports = userImports?.env;
|
|
166
|
+
if (userEnvImports) {
|
|
167
|
+
userCustomEnvImports = { ...userImports };
|
|
168
|
+
delete userCustomEnvImports.env;
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw createPoolErrorFromAnyError(`Error creating user WASM Imports`, POOL_ERROR_NAMES.PoolConfigError, error);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
userEnvImports,
|
|
175
|
+
userCustomEnvImports
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create import object for test discovery
|
|
180
|
+
*/
|
|
181
|
+
function createDiscoveryImports(memory, module, file, handleLog, logPrefix, coverageMemory, createWasmImports) {
|
|
182
|
+
const suiteStack = [file];
|
|
183
|
+
const { userEnvImports, userCustomEnvImports } = createUserWasmImports(createWasmImports, memory, module, logPrefix);
|
|
184
|
+
return {
|
|
185
|
+
env: {
|
|
186
|
+
...createWasmConsole(memory, handleLog),
|
|
187
|
+
...userEnvImports ?? {},
|
|
188
|
+
memory,
|
|
189
|
+
abort(msgPtr, filePtr, line, column) {
|
|
190
|
+
const { message, location } = decodeAbortInfo(memory, msgPtr, filePtr, line, column);
|
|
191
|
+
const msgAtLoc = `${message}${location ? ` at ${location}` : ""}`;
|
|
192
|
+
debug(`${logPrefix} - Unexpected abort during test discovery: ${msgAtLoc}`);
|
|
193
|
+
const rawCallStack = extractCallStack(/* @__PURE__ */ new Error());
|
|
194
|
+
const testError = {
|
|
195
|
+
message,
|
|
196
|
+
name: TEST_ERROR_NAMES.WASMRuntimeError
|
|
197
|
+
};
|
|
198
|
+
throw createPoolError(msgAtLoc, POOL_ERROR_NAMES.WASMExecutionAbortError, void 0, testError, rawCallStack);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
["__as_pool_env__"]: {
|
|
202
|
+
...coverageMemory ? { __coverage_memory: coverageMemory } : {},
|
|
203
|
+
__assertion_pass() {},
|
|
204
|
+
__assertion_fail() {},
|
|
205
|
+
__expect_throw() {},
|
|
206
|
+
__end_expect_throw() {},
|
|
207
|
+
__begin_register_suite(namePtr, timeout, retry, skip, only, fails) {
|
|
208
|
+
const parentSuite = suiteStack[suiteStack.length - 1];
|
|
209
|
+
const defaultTestOptions = parentSuite.meta.defaultTestOptions;
|
|
210
|
+
const suiteName = liftString(memory, namePtr) ?? "unknown suite";
|
|
211
|
+
const options = mergeAssemblyScriptTestOptions(defaultTestOptions, timeout, retry, skip, only, fails);
|
|
212
|
+
const suite = createSuiteTask(suiteName, file, parentSuite, options);
|
|
213
|
+
suiteStack.push(suite);
|
|
214
|
+
debug(`${logPrefix} - Registering Suite "${suite.name}" | timeout: ${options.timeout} ms | retry: ${options.retry} | skip: ${options.skip} | only: ${options.only} | fails: ${options.fails} | parent: "${suite.suite?.name}" (parent idx: ${suite.meta.idxInParentTasks})`);
|
|
215
|
+
},
|
|
216
|
+
__end_register_suite(_namePtr) {
|
|
217
|
+
const suite = suiteStack.pop();
|
|
218
|
+
debug(`${logPrefix} - Registered Suite "${suite?.name}" | ${suite?.tasks.length} top-level tasks | mode: "${suite?.mode}" | parent: "${suite?.suite?.name}" (parent idx: ${(suite?.meta)?.idxInParentTasks})`);
|
|
219
|
+
},
|
|
220
|
+
__register_test(namePtr, fnIndex, timeout, retry, skip, only, fails) {
|
|
221
|
+
const parentSuite = suiteStack[suiteStack.length - 1];
|
|
222
|
+
const defaultTestOptions = parentSuite.meta.defaultTestOptions;
|
|
223
|
+
const testName = liftString(memory, namePtr) ?? "unknown test";
|
|
224
|
+
const options = mergeAssemblyScriptTestOptions(defaultTestOptions, timeout, retry, skip, only, fails);
|
|
225
|
+
const test = createTestTask(testName, fnIndex, file, parentSuite, options);
|
|
226
|
+
debug(`${logPrefix} - Registered test "${test.name}" | mode (pre-interp): "${test.mode}" | fnIndex ${fnIndex} | timeout: ${options.timeout} ms | retry: ${options.retry} | skip: ${options.skip} | only: ${options.only} | fails: ${options.fails} | suite: "${test.suite?.name}" (parent idx: ${test.meta.idxInParentTasks})`);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
...userCustomEnvImports ?? {}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Create import object for test execution
|
|
234
|
+
*
|
|
235
|
+
* Used during test execution to capture test results / assertions, and to handle
|
|
236
|
+
* runtime aborts as expected cases for test execution by capturing the error on the test meta.
|
|
237
|
+
*/
|
|
238
|
+
function createTestExecutionImports(memory, module, test, handleLog, logPrefix, coverageMemory, createWasmImports) {
|
|
239
|
+
let isExpectingError = false;
|
|
240
|
+
let expectedErrorMsgStr;
|
|
241
|
+
let wasmFunctionTable;
|
|
242
|
+
const { userEnvImports, userCustomEnvImports } = createUserWasmImports(createWasmImports, memory, module, logPrefix);
|
|
243
|
+
return {
|
|
244
|
+
imports: {
|
|
245
|
+
env: {
|
|
246
|
+
...createWasmConsole(memory, handleLog),
|
|
247
|
+
...userEnvImports ?? {},
|
|
248
|
+
memory,
|
|
249
|
+
abort(msgPtr, filePtr, line, column) {
|
|
250
|
+
const { message, location } = decodeAbortInfo(memory, msgPtr, filePtr, line, column);
|
|
251
|
+
debug(`${logPrefix} - Handling test execution abort: ${`${message}${location ? ` at ${location}` : ""}`}`);
|
|
252
|
+
let failureMessage = message;
|
|
253
|
+
if (isExpectingError) if (!expectedErrorMsgStr || message.includes(expectedErrorMsgStr)) {
|
|
254
|
+
test.meta.assertionsPassedCount++;
|
|
255
|
+
debug(`${logPrefix} - Thrown error matches expected - assertion passes`);
|
|
256
|
+
throw createPoolError(`AssemblyScript abort() import called for expected error throw in test "${test.name}"`, POOL_ERROR_NAMES.WASMExecutionAbortError);
|
|
257
|
+
} else {
|
|
258
|
+
failureMessage = `expected function to throw "${expectedErrorMsgStr}" error but received "${message}"`;
|
|
259
|
+
test.meta.assertionsFailed.push({
|
|
260
|
+
message: failureMessage,
|
|
261
|
+
typeName: "Error",
|
|
262
|
+
valuesProvided: true,
|
|
263
|
+
actual: message,
|
|
264
|
+
expected: expectedErrorMsgStr
|
|
265
|
+
});
|
|
266
|
+
debug(`${logPrefix} - Assertion failed: ${`Thrown error does not match expected | Expected: "${expectedErrorMsgStr}" | Actual: "${message}"`}`);
|
|
267
|
+
}
|
|
268
|
+
failTest(test, failureMessage, /* @__PURE__ */ new Error(), logPrefix);
|
|
269
|
+
throw createPoolError(`AssemblyScript abort() import called during test execution for ${test.name}`, POOL_ERROR_NAMES.WASMExecutionAbortError);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
["__as_pool_env__"]: {
|
|
273
|
+
...coverageMemory ? { __coverage_memory: coverageMemory } : {},
|
|
274
|
+
__register_test() {},
|
|
275
|
+
__begin_register_suite() {},
|
|
276
|
+
__end_register_suite() {},
|
|
277
|
+
__assertion_pass() {
|
|
278
|
+
test.meta.assertionsPassedCount++;
|
|
279
|
+
},
|
|
280
|
+
__assertion_fail(msgPtr, typeNamePtr, valuesProvided, actualPtr, expectedPtr) {
|
|
281
|
+
const errorMsg = liftString(memory, msgPtr);
|
|
282
|
+
const assertionValueType = liftString(memory, typeNamePtr);
|
|
283
|
+
let actual;
|
|
284
|
+
let expected;
|
|
285
|
+
const assertionFailure = {
|
|
286
|
+
message: errorMsg,
|
|
287
|
+
typeName: assertionValueType,
|
|
288
|
+
valuesProvided: Boolean(valuesProvided)
|
|
289
|
+
};
|
|
290
|
+
if (valuesProvided && actualPtr && expectedPtr) {
|
|
291
|
+
assertionFailure.actual = liftString(memory, actualPtr);
|
|
292
|
+
assertionFailure.expected = liftString(memory, expectedPtr);
|
|
293
|
+
}
|
|
294
|
+
test.meta.assertionsFailed.push(assertionFailure);
|
|
295
|
+
debug(`${logPrefix} - Assertion failed: ${errorMsg}${valuesProvided ? ` | Value Type: ${assertionValueType} | Expected: \`${expected !== void 0 ? expected : ""}\` | Actual: \`${actual !== void 0 ? actual : ""}\`` : ""}`);
|
|
296
|
+
},
|
|
297
|
+
__expect_throw(fnIndex, expectedErrorMsgPtr) {
|
|
298
|
+
isExpectingError = true;
|
|
299
|
+
if (expectedErrorMsgPtr) expectedErrorMsgStr = liftString(memory, expectedErrorMsgPtr);
|
|
300
|
+
debug(`${logPrefix} - Registered expected error throw: ${expectedErrorMsgStr !== void 0 ? `"${expectedErrorMsgStr}"` : "<any>"}`);
|
|
301
|
+
if (wasmFunctionTable && typeof wasmFunctionTable.get === "function") {
|
|
302
|
+
const fn = wasmFunctionTable.get(fnIndex);
|
|
303
|
+
if (!fn) throw createPoolError(`Could not access function (fnPtr ${fnIndex}) which is expected to throw in test "${test.name}"`, POOL_ERROR_NAMES.WASMExecutionHarnessError);
|
|
304
|
+
debug(`${logPrefix} - Calling function (idx ${fnIndex})`);
|
|
305
|
+
fn();
|
|
306
|
+
} else throw createPoolError(`Could not access WASM function table to call function expected to throw in test "${test.name}"`, POOL_ERROR_NAMES.WASMExecutionHarnessError);
|
|
307
|
+
},
|
|
308
|
+
__end_expect_throw() {
|
|
309
|
+
if (isExpectingError) {
|
|
310
|
+
const isAnyErr = !!expectedErrorMsgStr;
|
|
311
|
+
const failureMessage = `expected function to throw ${isAnyErr ? `"${expectedErrorMsgStr}"` : "any"} error - did not throw`;
|
|
312
|
+
test.meta.assertionsFailed.push({
|
|
313
|
+
message: failureMessage,
|
|
314
|
+
typeName: "Error",
|
|
315
|
+
valuesProvided: !isAnyErr,
|
|
316
|
+
actual: void 0,
|
|
317
|
+
expected: expectedErrorMsgStr
|
|
318
|
+
});
|
|
319
|
+
debug(`${logPrefix} - Assertion failed: ${`Expected thrown error but got none | Expected: "${expectedErrorMsgStr}"`}`);
|
|
320
|
+
failTest(test, failureMessage, /* @__PURE__ */ new Error(), logPrefix);
|
|
321
|
+
throw createPoolError(`AssemblyScript __end_expect_throw() import called during test execution for ${test.name}`, POOL_ERROR_NAMES.WASMExecutionAbortError);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
...userCustomEnvImports ?? {}
|
|
326
|
+
},
|
|
327
|
+
provideFunctionTable: (table) => {
|
|
328
|
+
debug(`${logPrefix} - Got WASM function table | length: ${table.length}`);
|
|
329
|
+
wasmFunctionTable = table;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/wasm-executor/wasm-names.ts
|
|
336
|
+
/**
|
|
337
|
+
* Extracts the short name from a WASM function table name identifier.
|
|
338
|
+
*/
|
|
339
|
+
function getShortFunctionName(fullName) {
|
|
340
|
+
if (!fullName) return "";
|
|
341
|
+
let decoded;
|
|
342
|
+
try {
|
|
343
|
+
decoded = decodeURIComponent(fullName);
|
|
344
|
+
} catch {
|
|
345
|
+
decoded = fullName;
|
|
346
|
+
}
|
|
347
|
+
let angleBracketDepth = 0;
|
|
348
|
+
let parenDepth = 0;
|
|
349
|
+
let lastSlashOutsideBrackets = -1;
|
|
350
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
351
|
+
const char = decoded[i];
|
|
352
|
+
if (char === "<") angleBracketDepth++;
|
|
353
|
+
else if (char === ">" && decoded[i - 1] !== "=") angleBracketDepth--;
|
|
354
|
+
else if (char === "(") parenDepth++;
|
|
355
|
+
else if (char === ")") parenDepth--;
|
|
356
|
+
else if (char === "/" && angleBracketDepth === 0 && parenDepth === 0) lastSlashOutsideBrackets = i;
|
|
357
|
+
}
|
|
358
|
+
const functionPart = lastSlashOutsideBrackets >= 0 ? decoded.substring(lastSlashOutsideBrackets + 1) : decoded;
|
|
359
|
+
const anonymousMatch = functionPart.match(/^.+~(anonymous\|\d+)$/);
|
|
360
|
+
if (anonymousMatch) return anonymousMatch[1];
|
|
361
|
+
return shortenTypePart(functionPart);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Finds the index of the closing '>' that matches the opening '<' at openIndex.
|
|
365
|
+
*/
|
|
366
|
+
function findMatchingCloseBracket(str, openIndex) {
|
|
367
|
+
let angleBracketDepth = 1;
|
|
368
|
+
for (let i = openIndex + 1; i < str.length; i++) {
|
|
369
|
+
const char = str[i];
|
|
370
|
+
if (char === "<") angleBracketDepth++;
|
|
371
|
+
else if (char === ">" && str[i - 1] !== "=") {
|
|
372
|
+
angleBracketDepth--;
|
|
373
|
+
if (angleBracketDepth === 0) return i;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return str.length - 1;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Finds the index of the closing ')' that matches the opening '(' at openIndex.
|
|
380
|
+
*/
|
|
381
|
+
function findMatchingCloseParen(str, openIndex) {
|
|
382
|
+
let parenDepth = 1;
|
|
383
|
+
let angleBracketDepth = 0;
|
|
384
|
+
for (let i = openIndex + 1; i < str.length; i++) {
|
|
385
|
+
const char = str[i];
|
|
386
|
+
if (char === "(") parenDepth++;
|
|
387
|
+
else if (char === ")") {
|
|
388
|
+
parenDepth--;
|
|
389
|
+
if (parenDepth === 0) return i;
|
|
390
|
+
} else if (char === "<") angleBracketDepth++;
|
|
391
|
+
else if (char === ">" && str[i - 1] !== "=") angleBracketDepth--;
|
|
392
|
+
}
|
|
393
|
+
return str.length - 1;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Splits a string by commas at the top level (not inside <> or ()).
|
|
397
|
+
*/
|
|
398
|
+
function splitByTopLevelComma(str) {
|
|
399
|
+
const parts = [];
|
|
400
|
+
let current = "";
|
|
401
|
+
let angleBracketDepth = 0;
|
|
402
|
+
let parenDepth = 0;
|
|
403
|
+
for (let i = 0; i < str.length; i++) {
|
|
404
|
+
const char = str[i];
|
|
405
|
+
if (char === "<") angleBracketDepth++;
|
|
406
|
+
else if (char === ">" && str[i - 1] !== "=") angleBracketDepth--;
|
|
407
|
+
else if (char === "(") parenDepth++;
|
|
408
|
+
else if (char === ")") parenDepth--;
|
|
409
|
+
else if (char === "," && angleBracketDepth === 0 && parenDepth === 0) {
|
|
410
|
+
parts.push(current);
|
|
411
|
+
current = "";
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
current += char;
|
|
415
|
+
}
|
|
416
|
+
parts.push(current);
|
|
417
|
+
return parts;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Processes the content inside generic brackets or function args.
|
|
421
|
+
*/
|
|
422
|
+
function shortenGenericContent(content) {
|
|
423
|
+
return splitByTopLevelComma(content).map((part) => shortenTypePart(part.trim())).join(",");
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Shortens a function type like (args)=>returnType.
|
|
427
|
+
*/
|
|
428
|
+
function shortenFunctionType(part) {
|
|
429
|
+
const closeParenIndex = findMatchingCloseParen(part, 0);
|
|
430
|
+
const argsContent = part.substring(1, closeParenIndex);
|
|
431
|
+
const afterParen = part.substring(closeParenIndex + 1);
|
|
432
|
+
const shortenedArgs = argsContent ? shortenGenericContent(argsContent) : "";
|
|
433
|
+
let returnPart = afterParen;
|
|
434
|
+
if (afterParen.startsWith("=>") && afterParen.length > 2) returnPart = "=>" + shortenTypePart(afterParen.substring(2));
|
|
435
|
+
return "(" + shortenedArgs + ")" + returnPart;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Shortens a type/function part, processing paths and generics recursively.
|
|
439
|
+
*/
|
|
440
|
+
function shortenTypePart(part) {
|
|
441
|
+
if (part.startsWith("(")) return shortenFunctionType(part);
|
|
442
|
+
const openBracket = part.indexOf("<");
|
|
443
|
+
if (openBracket === -1) {
|
|
444
|
+
if (!part.includes("/")) return part;
|
|
445
|
+
return part.substring(part.lastIndexOf("/") + 1);
|
|
446
|
+
}
|
|
447
|
+
const namePart = part.substring(0, openBracket);
|
|
448
|
+
const closeBracket = findMatchingCloseBracket(part, openBracket);
|
|
449
|
+
const genericContent = part.substring(openBracket + 1, closeBracket);
|
|
450
|
+
const name = namePart.includes("/") ? namePart.substring(namePart.lastIndexOf("/") + 1) : namePart;
|
|
451
|
+
const shortenedContent = shortenGenericContent(genericContent);
|
|
452
|
+
return name + "<" + shortenedContent + ">";
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/wasm-executor/wasm-errors.ts
|
|
457
|
+
const POOL_INTERNAL_PATHS_SET = new Set(POOL_INTERNAL_PATHS);
|
|
458
|
+
async function sourceMapRawCallStack(rawCallStack, sourceMap, loggingPrefix) {
|
|
459
|
+
const mappedStack = [];
|
|
460
|
+
if (!rawCallStack || rawCallStack.length === 0) return mappedStack;
|
|
461
|
+
const sourceMapConsumer = await new SourceMapConsumer(sourceMap);
|
|
462
|
+
rawCallStack.forEach((callSite) => {
|
|
463
|
+
const mappedCallSite = createWebAssemblyCallSite(callSite, sourceMapConsumer, loggingPrefix);
|
|
464
|
+
if (mappedCallSite) mappedStack.push(mappedCallSite);
|
|
465
|
+
});
|
|
466
|
+
sourceMapConsumer.destroy();
|
|
467
|
+
return mappedStack;
|
|
468
|
+
}
|
|
469
|
+
function parseMappedStack(mappedStack, isAssertionFailure) {
|
|
470
|
+
return mappedStack.filter((frame) => !(isAssertionFailure && POOL_INTERNAL_PATHS_SET.has(frame.location.filePath))).map((frame) => ({
|
|
471
|
+
method: getShortFunctionName(frame.functionName),
|
|
472
|
+
file: frame.location.filePath,
|
|
473
|
+
line: frame.location.line,
|
|
474
|
+
column: frame.location.column + 1
|
|
475
|
+
}));
|
|
476
|
+
}
|
|
477
|
+
async function processWASMErrorStack(rawCallStack, sourceMap, isAssertionFailure, loggingPrefix) {
|
|
478
|
+
const sourceMapObj = parseSourceMap(sourceMap);
|
|
479
|
+
const sourceMappedStack = await sourceMapRawCallStack(rawCallStack, sourceMapObj, loggingPrefix);
|
|
480
|
+
debug(`${loggingPrefix} - Mapped ${rawCallStack.length} call sites to ${sourceMappedStack.length} source locations`);
|
|
481
|
+
return {
|
|
482
|
+
parsedStack: parseMappedStack(sourceMappedStack, isAssertionFailure),
|
|
483
|
+
parsedSourceMap: sourceMapObj
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Enhance reportable test error on the provided test result with source mapped stack locations
|
|
488
|
+
* and a formatted diff based on the error type
|
|
489
|
+
*/
|
|
490
|
+
async function enhanceTestError(error, task, sourceMap, valuesProvided, logPrefix, highlight, rawCallStack, diffOptions) {
|
|
491
|
+
const isAssertionFailure = error.name === TEST_ERROR_NAMES.AssertionError;
|
|
492
|
+
let expectedVsActualDiffString = "";
|
|
493
|
+
if (isAssertionFailure && valuesProvided) expectedVsActualDiffString = diff(error.expected, error.actual, diffOptions) ?? "";
|
|
494
|
+
if (!rawCallStack || rawCallStack.length === 0) {
|
|
495
|
+
error.diff = expectedVsActualDiffString;
|
|
496
|
+
error.stack = `${task.name} - ${error.message}`;
|
|
497
|
+
return error;
|
|
498
|
+
}
|
|
499
|
+
const { parsedStack, parsedSourceMap } = await processWASMErrorStack(rawCallStack, sourceMap, isAssertionFailure, logPrefix);
|
|
500
|
+
let primaryStackFrameString;
|
|
501
|
+
let highlightedSourceCodeFrameString;
|
|
502
|
+
if (parsedStack.length > 0) {
|
|
503
|
+
const primaryStackFrame = parsedStack[0];
|
|
504
|
+
primaryStackFrameString = toVitestLikeStackFrameString(primaryStackFrame);
|
|
505
|
+
error.stacks = parsedStack.slice(1);
|
|
506
|
+
highlightedSourceCodeFrameString = getSourceCodeFrameString(parsedSourceMap, primaryStackFrame, highlight);
|
|
507
|
+
debug(`${logPrefix} - Enhanced ${error.name} error with parsed source stack`);
|
|
508
|
+
}
|
|
509
|
+
if (isAssertionFailure) error.diff = [
|
|
510
|
+
`${expectedVsActualDiffString}${expectedVsActualDiffString ? "\n\n" : ""}`,
|
|
511
|
+
`${primaryStackFrameString}\n`,
|
|
512
|
+
`${highlightedSourceCodeFrameString}`
|
|
513
|
+
].join("");
|
|
514
|
+
else error.diff = [`${primaryStackFrameString}\n`, `${highlightedSourceCodeFrameString}`].join("");
|
|
515
|
+
error.stack = parsedStack.map(toPlaintextStackFrameString).join("\n");
|
|
516
|
+
debug(`[${logPrefix} - Enhanced ${error.name} error with diffs`);
|
|
517
|
+
return error;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/wasm-executor/index.ts
|
|
522
|
+
const SIG_MISMATCH_ERROR_MSG = "WASM RuntimeError indicates function signature type mismatch during test suite collection. This is likely caused by passing a non-void callback to expect(). Use braces to ensure it returns void e.g. `expect(() => { failingFunction(); }).toThrowError()`. Look for the failing expect() within the describe() block indicated in the stack trace.";
|
|
523
|
+
function covDebug(...args) {}
|
|
524
|
+
function createExecutorPoolError(testFileBasename, context, reason, cause) {
|
|
525
|
+
return createPoolError(`${testFileBasename} - ${context} WASM executor: ${reason}`, POOL_ERROR_NAMES.WASMExecutionHarnessError, void 0, cause);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Discover tests via test() and suites via describe() registration calls
|
|
529
|
+
*/
|
|
530
|
+
async function executeWASMDiscovery(binary, sourceMap, testFileBasename, poolOptions, isBinaryInstrumented, handleLog, file, moduleLabel, threadImports, diffOptions) {
|
|
531
|
+
const logPrefix = `[${moduleLabel} Exec] ${getTaskLogLabel(basename(file.filepath), file)}`;
|
|
532
|
+
const wasmModule = await WebAssembly.compile(binary);
|
|
533
|
+
const importObject = createDiscoveryImports(createMemory(poolOptions.testMemoryPagesInitial, poolOptions.testMemoryPagesMax), wasmModule, file, handleLog, logPrefix, isBinaryInstrumented ? createMemory(poolOptions.coverageMemoryPagesInitial, poolOptions.coverageMemoryPagesMax) : void 0, threadImports.createWasmImports);
|
|
534
|
+
const exports = new WebAssembly.Instance(wasmModule, importObject).exports;
|
|
535
|
+
if (typeof exports._start === "function") try {
|
|
536
|
+
exports._start();
|
|
537
|
+
} catch (error) {
|
|
538
|
+
const thrownErrAny = error;
|
|
539
|
+
if (error instanceof WebAssembly.RuntimeError && thrownErrAny?.message.includes("null function or function signature mismatch")) {
|
|
540
|
+
const runtimeError = error;
|
|
541
|
+
const stack = extractCallStack(runtimeError);
|
|
542
|
+
const testError = await enhanceTestError({
|
|
543
|
+
name: TEST_ERROR_NAMES.WASMRuntimeError,
|
|
544
|
+
message: runtimeError.message
|
|
545
|
+
}, file, sourceMap, false, logPrefix, threadImports.highlight, stack, diffOptions);
|
|
546
|
+
throw createPoolError(`${SIG_MISMATCH_ERROR_MSG}\n Caused by: ${runtimeError.name}: ${runtimeError.message}`, POOL_ERROR_NAMES.PoolSyntaxError, void 0, testError);
|
|
547
|
+
}
|
|
548
|
+
if (thrownErrAny?.name === POOL_ERROR_NAMES.WASMExecutionAbortError && thrownErrAny?.cause?.name === TEST_ERROR_NAMES.WASMRuntimeError && error.rawCallStack) {
|
|
549
|
+
const thrownPoolErr = thrownErrAny;
|
|
550
|
+
thrownPoolErr.cause = await enhanceTestError(thrownPoolErr.cause, file, sourceMap, false, logPrefix, threadImports.highlight, thrownPoolErr.rawCallStack, diffOptions);
|
|
551
|
+
thrownPoolErr.causeIsEnhancedError = true;
|
|
552
|
+
delete thrownPoolErr.rawCallStack;
|
|
553
|
+
throw thrownPoolErr;
|
|
554
|
+
} else throw createPoolErrorFromAnyError(`${testFileBasename} - Unexpected discovery error`, POOL_ERROR_NAMES.WASMExecutionHarnessError, error);
|
|
555
|
+
}
|
|
556
|
+
else throw createExecutorPoolError(testFileBasename, "discoverTests", "no _start() export");
|
|
557
|
+
debug(`${logPrefix} - Discovered ${file.tasks.length} top-level tasks`);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Execute a single test with crash isolation
|
|
561
|
+
*/
|
|
562
|
+
async function executeWASMTest(test, compilation, testFileBasename, poolOptions, collectCoverage, handleLog, moduleLabel, threadImports, diffOptions) {
|
|
563
|
+
const testTimings = {
|
|
564
|
+
fnInit: performance.now(),
|
|
565
|
+
execStart: 0,
|
|
566
|
+
execEnd: 0,
|
|
567
|
+
fnfinal: 0
|
|
568
|
+
};
|
|
569
|
+
const base = basename(test.file.filepath);
|
|
570
|
+
const logPrefix = `[${`${moduleLabel} Exec`}] ${getTaskLogLabel(base, test)}`;
|
|
571
|
+
const wasmModule = await WebAssembly.compile(compilation.binary);
|
|
572
|
+
const memory = createMemory(poolOptions.testMemoryPagesInitial, poolOptions.testMemoryPagesMax);
|
|
573
|
+
const coverageMemory = collectCoverage ? createMemory(poolOptions.coverageMemoryPagesInitial, poolOptions.coverageMemoryPagesMax) : void 0;
|
|
574
|
+
const { imports, provideFunctionTable } = createTestExecutionImports(memory, wasmModule, test, handleLog, logPrefix, coverageMemory, threadImports.createWasmImports);
|
|
575
|
+
const exports = new WebAssembly.Instance(wasmModule, imports).exports;
|
|
576
|
+
const table = exports.table;
|
|
577
|
+
if (table && typeof table.get === "function") provideFunctionTable(table);
|
|
578
|
+
if (typeof exports._start === "function") exports._start();
|
|
579
|
+
else throw createExecutorPoolError(testFileBasename, "executeWASMTest", "no _start() export");
|
|
580
|
+
let testFn;
|
|
581
|
+
if (table && typeof table.get === "function") {
|
|
582
|
+
const idx = test.meta.fnIndex;
|
|
583
|
+
testFn = table.get(idx);
|
|
584
|
+
if (!testFn) throw createExecutorPoolError(testFileBasename, "executeWASMTest", `Test function at index ${idx} not found in function table`);
|
|
585
|
+
} else throw createExecutorPoolError(testFileBasename, "executeWASMTest", "Function table not found in WASM exports (missing --exportTable flag?)");
|
|
586
|
+
try {
|
|
587
|
+
testTimings.execStart = performance.now();
|
|
588
|
+
testFn();
|
|
589
|
+
testTimings.execEnd = performance.now();
|
|
590
|
+
} catch (error) {
|
|
591
|
+
testTimings.execEnd = performance.now();
|
|
592
|
+
if (error?.name !== POOL_ERROR_NAMES.WASMExecutionAbortError) throw createExecutorPoolError(testFileBasename, "executeWASMTest", `Unexpected execution error: ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}`, error?.cause);
|
|
593
|
+
}
|
|
594
|
+
const meta = test.meta;
|
|
595
|
+
if (meta.lastError) {
|
|
596
|
+
const enhancedError = await enhanceTestError(meta.lastError, test, compilation.sourceMap, meta.lastErrorValuesProvided ?? false, logPrefix, threadImports.highlight, meta.lastErrorRawCallStack, diffOptions);
|
|
597
|
+
if (test.result) if (test.result.errors) test.result.errors.push(enhancedError);
|
|
598
|
+
else test.result.errors = [enhancedError];
|
|
599
|
+
delete meta.lastError;
|
|
600
|
+
delete meta.lastErrorValuesProvided;
|
|
601
|
+
delete meta.lastErrorRawCallStack;
|
|
602
|
+
}
|
|
603
|
+
if (collectCoverage) {
|
|
604
|
+
if (!coverageMemory) throw createExecutorPoolError(testFileBasename, "executeWASMTest", "Coverage memory not created despite collectCoverage=true");
|
|
605
|
+
if (!compilation.debugInfo) throw createExecutorPoolError(testFileBasename, "executeWASMTest", "debugInfo is required when collectCoverage=true");
|
|
606
|
+
const coverage = { hitCountsByFileAndPosition: {} };
|
|
607
|
+
const extractedHitCounters = new Uint32Array(coverageMemory.buffer, 0, compilation.debugInfo.instrumentedFunctionCount);
|
|
608
|
+
covDebug(`${logPrefix} - Read coverage memory for ${compilation.debugInfo.instrumentedFunctionCount} instrumented functions`);
|
|
609
|
+
let functionsHit = 0;
|
|
610
|
+
for (const [filePath, debugFunctions] of Object.entries(compilation.debugInfo.functionsByFileAndPosition)) {
|
|
611
|
+
if (!coverage.hitCountsByFileAndPosition[filePath]) {
|
|
612
|
+
coverage.hitCountsByFileAndPosition[filePath] = {};
|
|
613
|
+
covDebug(`${logPrefix} - Extracting hits for source file "${filePath}"`);
|
|
614
|
+
}
|
|
615
|
+
for (const [positionKey, funcInfo] of Object.entries(debugFunctions)) {
|
|
616
|
+
if (funcInfo.coverageMemoryIndex === void 0) {
|
|
617
|
+
debug(`${logPrefix} - WARNING: NO COVERAGE MEMORY INDEX - func "${funcInfo.name}" (${positionKey}) Skipping hit extraction`);
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const hitCount = extractedHitCounters[funcInfo.coverageMemoryIndex] ?? 0;
|
|
621
|
+
covDebug(`${logPrefix} - func "${funcInfo.name}" (${positionKey}) [idx: ${funcInfo.coverageMemoryIndex}]: ${hitCount} hits`);
|
|
622
|
+
if (coverage.hitCountsByFileAndPosition[filePath][positionKey] !== void 0) debug(`${logPrefix} - WARNING: DUPLICATE POSITION - func "${funcInfo.name}" (${positionKey}) already extracted to coverage for ${filePath}`);
|
|
623
|
+
coverage.hitCountsByFileAndPosition[filePath][positionKey] = hitCount;
|
|
624
|
+
if (hitCount > 0) functionsHit++;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
meta.coverageData = coverage;
|
|
628
|
+
debug(`${logPrefix} - Extracted coverage data | ${functionsHit} functions hit`);
|
|
629
|
+
}
|
|
630
|
+
testTimings.fnfinal = performance.now();
|
|
631
|
+
return {
|
|
632
|
+
test,
|
|
633
|
+
testTimings
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region src/pool-thread/rpc-reporter.ts
|
|
639
|
+
const DEBUG_RPC = isDebugModeEnabled();
|
|
640
|
+
function rpcDebug(...args) {
|
|
641
|
+
if (DEBUG_RPC) debug(...args);
|
|
642
|
+
}
|
|
643
|
+
/** Create RPC client from MessagePort */
|
|
644
|
+
function createRpcClient(port) {
|
|
645
|
+
return createBirpc({ onCancel: (_reason) => void 0 }, {
|
|
646
|
+
post: (v) => port.postMessage(v),
|
|
647
|
+
on: (fn) => port.on("message", fn)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/** Report file as queued (before compilation & discovery starts) */
|
|
651
|
+
async function reportFileQueued(rpc, fileTask, logModule, logLabel) {
|
|
652
|
+
await rpc.onQueued(fileTask);
|
|
653
|
+
rpcDebug(`[${logModule} RPC] ${logLabel} - Reported onQueued for file "${fileTask.filepath}" | mode: "${fileTask.mode}" | state: "${fileTask.result ? fileTask.result.state : "--"}"`);
|
|
654
|
+
}
|
|
655
|
+
/** Report file collection complete with full task tree */
|
|
656
|
+
async function reportFileCollected(rpc, fileTask, logModule, logLabel) {
|
|
657
|
+
await rpc.onCollected([fileTask]);
|
|
658
|
+
rpcDebug(`[${logModule} RPC] ${logLabel} - Reported onCollected for file "${fileTask.filepath}" | ${fileTask.tasks.length} tasks | mode: "${fileTask.mode}" | state: "${fileTask.result?.state}"`);
|
|
659
|
+
}
|
|
660
|
+
/** Report file-level error (compilation/discovery failure) as "suite-failed-early" */
|
|
661
|
+
async function reportFileError(rpc, fileTask, logModule, logLabel) {
|
|
662
|
+
const taskPack = [
|
|
663
|
+
fileTask.id,
|
|
664
|
+
fileTask.result,
|
|
665
|
+
{}
|
|
666
|
+
];
|
|
667
|
+
const eventPack = [
|
|
668
|
+
fileTask.id,
|
|
669
|
+
"suite-failed-early",
|
|
670
|
+
void 0
|
|
671
|
+
];
|
|
672
|
+
await rpc.onTaskUpdate([taskPack], [eventPack]);
|
|
673
|
+
rpcDebug(`[${logModule} RPC] ${logLabel} - Reported "suite-failed-early" task update for "${fileTask.filepath}"`);
|
|
674
|
+
}
|
|
675
|
+
/** Report suite-prepare event */
|
|
676
|
+
async function reportSuitePrepare(rpc, suite, logModule, base) {
|
|
677
|
+
const taskPack = [
|
|
678
|
+
suite.id,
|
|
679
|
+
suite.result,
|
|
680
|
+
{}
|
|
681
|
+
];
|
|
682
|
+
const eventPack = [
|
|
683
|
+
suite.id,
|
|
684
|
+
"suite-prepare",
|
|
685
|
+
void 0
|
|
686
|
+
];
|
|
687
|
+
await rpc.onTaskUpdate([taskPack], [eventPack]);
|
|
688
|
+
rpcDebug(`[${logModule} RPC] ${getTaskLogLabel(base, suite)} - Reported "suite-prepare" task update | state: "${suite.result?.state}"`);
|
|
689
|
+
}
|
|
690
|
+
/** Report suite-finished event */
|
|
691
|
+
async function reportSuiteFinished(rpc, suite, logModule, base, vitestVersion = "v4") {
|
|
692
|
+
const suiteLabel = getTaskLogLabel(base, suite);
|
|
693
|
+
const rpcLogPrefix = `[${logModule} RPC] ${suiteLabel}`;
|
|
694
|
+
const meta = suite.meta;
|
|
695
|
+
const coverageKeys = Object.keys(meta.coverageData?.hitCountsByFileAndPosition ?? {}).length;
|
|
696
|
+
let coveragePromise = Promise.resolve();
|
|
697
|
+
if (isSuiteOwnFile(suite) && coverageKeys > 0) {
|
|
698
|
+
const afterSuiteMeta = createAfterSuiteRunMeta({
|
|
699
|
+
__format: COVERAGE_PAYLOAD_FORMATS.AssemblyScript,
|
|
700
|
+
coverageData: meta.coverageData,
|
|
701
|
+
suiteLogLabel: suiteLabel
|
|
702
|
+
}, [suite.file.filepath], suite.file.projectName, vitestVersion);
|
|
703
|
+
coveragePromise = rpc.onAfterSuiteRun(afterSuiteMeta);
|
|
704
|
+
debug(`${rpcLogPrefix} - onAfterSuiteRun: Reported suite coverage (${coverageKeys} unique positions)`);
|
|
705
|
+
} else if (coverageKeys === 0) debug(`${rpcLogPrefix} - onAfterSuiteRun: No suite coverage to report`);
|
|
706
|
+
const taskPack = [
|
|
707
|
+
suite.id,
|
|
708
|
+
suite.result,
|
|
709
|
+
{}
|
|
710
|
+
];
|
|
711
|
+
const eventPack = [
|
|
712
|
+
suite.id,
|
|
713
|
+
"suite-finished",
|
|
714
|
+
void 0
|
|
715
|
+
];
|
|
716
|
+
await Promise.all([coveragePromise, rpc.onTaskUpdate([taskPack], [eventPack])]);
|
|
717
|
+
rpcDebug(`${rpcLogPrefix} - Reported "suite-finished" task update | state: "${suite.result?.state}" | duration: ${suite.result?.duration?.toFixed(2) ?? "--"} ms`);
|
|
718
|
+
}
|
|
719
|
+
async function reportTestTaskUpdate(rpc, test, logModule, base, updateEvent) {
|
|
720
|
+
const taskPack = [
|
|
721
|
+
test.id,
|
|
722
|
+
test.result,
|
|
723
|
+
{}
|
|
724
|
+
];
|
|
725
|
+
const eventPack = [
|
|
726
|
+
test.id,
|
|
727
|
+
updateEvent,
|
|
728
|
+
void 0
|
|
729
|
+
];
|
|
730
|
+
rpcDebug(`[${logModule} RPC] ${getTaskLogLabel(base, test)} - Reporting "${updateEvent}" task update... | state: "${test.result?.state}"${updateEvent === "test-prepare" ? "" : ` | duration: ${test.result?.duration?.toFixed(2) ?? "--"} ms`}`);
|
|
731
|
+
await rpc.onTaskUpdate([taskPack], [eventPack]);
|
|
732
|
+
rpcDebug(`[${logModule} RPC] ${getTaskLogLabel(base, test)} - Reported "${updateEvent}" task update | state: "${test.result?.state}"${updateEvent === "test-prepare" ? "" : ` | duration: ${test.result?.duration?.toFixed(2) ?? "--"} ms`}`);
|
|
733
|
+
}
|
|
734
|
+
/** Report test starting execution */
|
|
735
|
+
async function reportTestPrepare(rpc, test, logModule, base) {
|
|
736
|
+
return reportTestTaskUpdate(rpc, test, logModule, base, "test-prepare");
|
|
737
|
+
}
|
|
738
|
+
/** Report test finished execution */
|
|
739
|
+
async function reportTestFinished(rpc, test, logModule, base) {
|
|
740
|
+
return reportTestTaskUpdate(rpc, test, logModule, base, "test-finished");
|
|
741
|
+
}
|
|
742
|
+
/** Report test retried (sent when test failed and is going to be retried) */
|
|
743
|
+
async function reportTestRetried(rpc, test, logModule, base) {
|
|
744
|
+
return reportTestTaskUpdate(rpc, test, logModule, base, "test-retried");
|
|
745
|
+
}
|
|
746
|
+
/** Report user console log messages */
|
|
747
|
+
async function reportUserConsoleLogs(rpc, logs, logModule, base, task) {
|
|
748
|
+
if (logs.length === 0) return;
|
|
749
|
+
const stdLogs = logs.filter((l) => !l.isError);
|
|
750
|
+
const errorLogs = logs.filter((l) => l.isError);
|
|
751
|
+
const stdContent = stdLogs.map((l) => `${l.msg}`).join("\n");
|
|
752
|
+
const errorContent = errorLogs.filter((l) => l.isError).map((l) => `${l.msg}`).join("\n");
|
|
753
|
+
const stdLog = {
|
|
754
|
+
content: `${stdContent}\n`,
|
|
755
|
+
size: stdContent.length,
|
|
756
|
+
browser: false,
|
|
757
|
+
type: "stdout",
|
|
758
|
+
time: stdLogs.length > 0 ? stdLogs[0].time : Date.now(),
|
|
759
|
+
taskId: task.id,
|
|
760
|
+
origin: task.id
|
|
761
|
+
};
|
|
762
|
+
const errorLog = {
|
|
763
|
+
content: `${errorContent}\n`,
|
|
764
|
+
size: errorContent.length,
|
|
765
|
+
browser: false,
|
|
766
|
+
type: "stderr",
|
|
767
|
+
time: errorLogs.length > 0 ? errorLogs[0].time : Date.now(),
|
|
768
|
+
taskId: task.id,
|
|
769
|
+
origin: task.id
|
|
770
|
+
};
|
|
771
|
+
const reportPromises = [];
|
|
772
|
+
if (stdContent.length > 0) reportPromises.push(rpc.onUserConsoleLog(stdLog));
|
|
773
|
+
if (errorContent.length > 0) reportPromises.push(rpc.onUserConsoleLog(errorLog));
|
|
774
|
+
await Promise.all(reportPromises);
|
|
775
|
+
rpcDebug(`[${logModule} RPC] ${getTaskLogLabel(base, task)} - Reported onUserConsoleLog | ${logs.length} messages`);
|
|
776
|
+
}
|
|
777
|
+
/** Flush any pending RPC updates */
|
|
778
|
+
async function flushRpcUpdates(rpc) {
|
|
779
|
+
await rpc.onTaskUpdate([], []);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/pool-thread/load-user-imports.ts
|
|
784
|
+
async function loadUserWasmImportsFactory(relativePath, projectRoot, logModule) {
|
|
785
|
+
if (!relativePath) return;
|
|
786
|
+
const path = resolve(projectRoot, relativePath);
|
|
787
|
+
try {
|
|
788
|
+
const start = performance.now();
|
|
789
|
+
const createWasmImports = (await import(path)).default;
|
|
790
|
+
debug(`[${logModule}] TIMING Imported user WasmImportsFactory in ${(performance.now() - start).toFixed(2)} ms`);
|
|
791
|
+
if (typeof createWasmImports !== "function") throw new Error(`User config for \`wasmImportsFactor\` must be the path to a module with a default export matching () => WebAssembly.Imports - Imported: "${typeof createWasmImports}": ${String(createWasmImports)}`);
|
|
792
|
+
else return createWasmImports;
|
|
793
|
+
} catch (error) {
|
|
794
|
+
if (error["__as_pool__"]) throw error;
|
|
795
|
+
throw new Error(`Could not load user WasmImportsFactory from "${path}". Ensure that your module path is relative to the project root (location of shallowest vitest config), and that it has a default export matching () => WebAssembly.Imports`, { cause: error });
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
//#endregion
|
|
800
|
+
export { createRpcClient, executeWASMDiscovery, executeWASMTest, flushRpcUpdates, loadUserWasmImportsFactory, reportFileCollected, reportFileError, reportFileQueued, reportSuiteFinished, reportSuitePrepare, reportTestFinished, reportTestPrepare, reportTestRetried, reportUserConsoleLogs };
|
|
801
|
+
//# sourceMappingURL=load-user-imports-J9eaAW0_.mjs.map
|