vitest-pool-assemblyscript 0.7.0 → 0.9.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/README.md +10 -13
- package/assembly/compare.ts +469 -76
- package/assembly/expect.ts +160 -67
- package/assembly/index.ts +0 -10
- package/assembly/utils.ts +168 -0
- package/dist/{addon-interface-_pNXcbib.mjs → addon-interface-CYFXMbK7.mjs} +4 -3
- package/dist/addon-interface-CYFXMbK7.mjs.map +1 -0
- package/dist/{ast-visitor-C5gQqWD2.mjs → ast-visitor-CWEOd3UH.mjs} +15 -2
- package/dist/ast-visitor-CWEOd3UH.mjs.map +1 -0
- package/dist/{compile-runner-DOMhsLQE.mjs → compile-runner-BGQhBkBo.mjs} +8 -7
- package/dist/compile-runner-BGQhBkBo.mjs.map +1 -0
- package/dist/compiler/transforms/deep-equals.d.mts +14 -0
- package/dist/compiler/transforms/deep-equals.d.mts.map +1 -0
- package/dist/compiler/transforms/deep-equals.mjs +245 -0
- package/dist/compiler/transforms/deep-equals.mjs.map +1 -0
- package/dist/compiler/transforms/strip-inline.mjs +2 -2
- package/dist/{compiler-CIXpfKq0.mjs → compiler-hUlDr5vL.mjs} +14 -6
- package/dist/compiler-hUlDr5vL.mjs.map +1 -0
- package/dist/config/index-v3.d.mts +33 -3
- package/dist/config/index-v3.d.mts.map +1 -1
- package/dist/config/index-v3.mjs.map +1 -1
- package/dist/config/index.d.mts +29 -4
- package/dist/config/index.d.mts.map +1 -0
- package/dist/config/index.mjs +6 -6
- package/dist/{constants-DuBLuMjt.mjs → constants-DbxJ3hzg.mjs} +10 -97
- package/dist/constants-DbxJ3hzg.mjs.map +1 -0
- package/dist/coverage-provider/index.mjs +30 -10
- package/dist/coverage-provider/index.mjs.map +1 -1
- package/dist/{debug-Cm1VFmaz.mjs → debug-DtRAL4rM.mjs} +38 -4
- package/dist/debug-DtRAL4rM.mjs.map +1 -0
- package/dist/{feature-check-ELxw_Mji.mjs → feature-check-Bje3ntpV.mjs} +2 -2
- package/dist/{feature-check-ELxw_Mji.mjs.map → feature-check-Bje3ntpV.mjs.map} +1 -1
- package/dist/index-internal.d.mts +1 -1
- package/dist/index-internal.d.mts.map +1 -1
- package/dist/index-internal.mjs +4 -4
- package/dist/index-v3.d.mts.map +1 -1
- package/dist/index-v3.mjs +8 -7
- package/dist/index-v3.mjs.map +1 -1
- package/dist/index.d.mts +1 -2
- package/dist/index.mjs +6 -6
- package/dist/{load-user-imports-B3Iy_K8k.mjs → load-user-imports-Bx5ZlhSm.mjs} +9 -9
- package/dist/load-user-imports-Bx5ZlhSm.mjs.map +1 -0
- package/dist/{pool-runner-init-BDQtAGwQ.mjs → pool-runner-init-BqkwQ2tk.mjs} +7 -7
- package/dist/pool-runner-init-BqkwQ2tk.mjs.map +1 -0
- package/dist/{pool-runner-init-CvnB0-iN.d.mts → pool-runner-init-CNpRdA5u.d.mts} +2 -2
- package/dist/pool-runner-init-CNpRdA5u.d.mts.map +1 -0
- package/dist/pool-thread/compile-worker-thread.d.mts +1 -1
- package/dist/pool-thread/compile-worker-thread.d.mts.map +1 -1
- package/dist/pool-thread/compile-worker-thread.mjs +10 -13
- package/dist/pool-thread/compile-worker-thread.mjs.map +1 -1
- package/dist/pool-thread/test-worker-thread.d.mts +1 -1
- package/dist/pool-thread/test-worker-thread.d.mts.map +1 -1
- package/dist/pool-thread/test-worker-thread.mjs +8 -11
- package/dist/pool-thread/test-worker-thread.mjs.map +1 -1
- package/dist/pool-thread/v3-tinypool-thread.d.mts +1 -1
- package/dist/pool-thread/v3-tinypool-thread.d.mts.map +1 -1
- package/dist/pool-thread/v3-tinypool-thread.mjs +12 -18
- package/dist/pool-thread/v3-tinypool-thread.mjs.map +1 -1
- package/dist/{resolve-config-DhZ4lOSK.mjs → resolve-config-s9gSJSMc.mjs} +14 -5
- package/dist/resolve-config-s9gSJSMc.mjs.map +1 -0
- package/dist/{test-runner-C4I9VknR.mjs → test-runner-BGqc9uCK.mjs} +4 -4
- package/dist/{test-runner-C4I9VknR.mjs.map → test-runner-BGqc9uCK.mjs.map} +1 -1
- package/dist/{types-D0nprJo1.d.mts → types-DHVk5iAx.d.mts} +17 -11
- package/dist/types-DHVk5iAx.d.mts.map +1 -0
- package/dist/vitest-file-tasks-D8sOClGX.mjs +149 -0
- package/dist/vitest-file-tasks-D8sOClGX.mjs.map +1 -0
- package/dist/{vitest-tasks--ow4pacR.mjs → vitest-tasks-BZ24sghI.mjs} +6 -4
- package/dist/vitest-tasks-BZ24sghI.mjs.map +1 -0
- package/package.json +11 -14
- package/prebuilds/darwin-arm64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/prebuilds/darwin-x64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/prebuilds/linux-arm64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/prebuilds/linux-x64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/prebuilds/linux-x64/vitest-pool-assemblyscript.musl.node +0 -0
- package/prebuilds/win32-arm64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/prebuilds/win32-x64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/src/instrumentation/native/addon.cpp +29 -3
- package/dist/addon-interface-_pNXcbib.mjs.map +0 -1
- package/dist/ast-visitor-C5gQqWD2.mjs.map +0 -1
- package/dist/compile-runner-DOMhsLQE.mjs.map +0 -1
- package/dist/compiler-CIXpfKq0.mjs.map +0 -1
- package/dist/constants-DuBLuMjt.mjs.map +0 -1
- package/dist/custom-provider-options-YTk1m7At.d.mts +0 -26
- package/dist/custom-provider-options-YTk1m7At.d.mts.map +0 -1
- package/dist/debug-Cm1VFmaz.mjs.map +0 -1
- package/dist/load-user-imports-B3Iy_K8k.mjs.map +0 -1
- package/dist/pool-runner-init-BDQtAGwQ.mjs.map +0 -1
- package/dist/pool-runner-init-CvnB0-iN.d.mts.map +0 -1
- package/dist/resolve-config-DhZ4lOSK.mjs.map +0 -1
- package/dist/types-D0nprJo1.d.mts.map +0 -1
- package/dist/vitest-file-tasks-Bn9CrWt_.mjs +0 -61
- package/dist/vitest-file-tasks-Bn9CrWt_.mjs.map +0 -1
- package/dist/vitest-tasks--ow4pacR.mjs.map +0 -1
package/assembly/expect.ts
CHANGED
|
@@ -2,12 +2,16 @@ import {
|
|
|
2
2
|
closeTo,
|
|
3
3
|
compareInequality,
|
|
4
4
|
equals,
|
|
5
|
+
equalsPathClear,
|
|
6
|
+
equalsPathString,
|
|
7
|
+
equalsRefPairsClear,
|
|
8
|
+
equalsRtmNamesClear,
|
|
9
|
+
equalsRtmNamesSuffix,
|
|
5
10
|
identical,
|
|
6
11
|
InequalityOperation,
|
|
7
|
-
|
|
8
|
-
nan,
|
|
9
|
-
truthyOrFalsey
|
|
12
|
+
truthyOrFalsey,
|
|
10
13
|
} from './compare';
|
|
14
|
+
import { isNull, nan, stringifyValue } from './utils';
|
|
11
15
|
|
|
12
16
|
// @external functions are imported to the WASM execution environment from pool code
|
|
13
17
|
|
|
@@ -34,43 +38,6 @@ declare function __expect_throw(fnPtr: usize, errorMsg?: string): void;
|
|
|
34
38
|
declare function __end_expect_throw(): void;
|
|
35
39
|
|
|
36
40
|
|
|
37
|
-
function itemMessageString<T>(item: T): string {
|
|
38
|
-
if (isNull(item)) return "null";
|
|
39
|
-
if (nan(item)) return "NaN";
|
|
40
|
-
|
|
41
|
-
if (isReference<T>()) {
|
|
42
|
-
if (isString<T>()) return `"${item}"`;
|
|
43
|
-
else if (item instanceof ArrayBuffer) return `ArrayBuffer[${item.byteLength}]`;
|
|
44
|
-
else if (isArrayLike<T>(item)) return arrayMessageString(item);
|
|
45
|
-
else if (item instanceof Set || item instanceof Map) return item.toString();
|
|
46
|
-
else return nameof<T>(item);
|
|
47
|
-
} else if (isBoolean<T>()){
|
|
48
|
-
return bool(item).toString();
|
|
49
|
-
} else if (isInteger<T>(item) || isFloat<T>(item)) {
|
|
50
|
-
return item.toString();
|
|
51
|
-
} else {
|
|
52
|
-
return nameof<T>(item);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function arrayMessageString<T extends ArrayLike<unknown>>(array: T): string {
|
|
57
|
-
if (isNullable<T>(array) && array == null) {
|
|
58
|
-
return "null";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let str = "[";
|
|
62
|
-
for (let i = 0; i < array.length; i++) {
|
|
63
|
-
str += itemMessageString(array[i]);
|
|
64
|
-
|
|
65
|
-
if (i < array.length - 1) {
|
|
66
|
-
str += ","
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
str += "]";
|
|
70
|
-
|
|
71
|
-
return str;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
41
|
/**
|
|
75
42
|
* Expect matcher
|
|
76
43
|
*/
|
|
@@ -96,7 +63,8 @@ abstract class BaseExpectMatcher<T> {
|
|
|
96
63
|
/**
|
|
97
64
|
* Checks that a value is what you expect using identity comparison. Primitives and strings
|
|
98
65
|
* are compared by value, and references are checked for reference equality only (including
|
|
99
|
-
* objects, arrays, etc).
|
|
66
|
+
* objects, arrays, etc). SIMD vectors use WASM's native `==` comparison, which compares at
|
|
67
|
+
* the bit level, ignoring lane type.
|
|
100
68
|
*
|
|
101
69
|
* Cross-type numeric comparisons are allowed where AssemblyScript's own `==` operator
|
|
102
70
|
* permits them (e.g. `f64` vs `i32`). `toBeCloseTo()` is safer for any comparison
|
|
@@ -105,6 +73,8 @@ abstract class BaseExpectMatcher<T> {
|
|
|
105
73
|
*
|
|
106
74
|
* @throws When comparing float/integer types where the float's mantissa cannot losslessly
|
|
107
75
|
* represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
|
|
76
|
+
* @throws When comparing fundamentally incompatible types: reference vs value type
|
|
77
|
+
* (e.g. `string` vs `i32`, unless one is null/zero), or `v128` vs non-vector type.
|
|
108
78
|
*
|
|
109
79
|
* @example
|
|
110
80
|
* expect(1 + 1).toBe(2);
|
|
@@ -116,6 +86,9 @@ abstract class BaseExpectMatcher<T> {
|
|
|
116
86
|
* // supported float/integer comparisons (small integer types)
|
|
117
87
|
* expect(f64(42.0)).toBe(i32(42));
|
|
118
88
|
*
|
|
89
|
+
* // SIMD vectors: different lane types with the same underlying bits are identical
|
|
90
|
+
* expect(i64x2(3, 7)).toBe(i32x4(3, 0, 7, 0));
|
|
91
|
+
*
|
|
119
92
|
* // unsupported float/integer comparisons throw an error
|
|
120
93
|
* // expect(f32(42.0)).toBe(i32(42)); // Error: float precision insufficient
|
|
121
94
|
* // expect(f64(42.0)).toBe(i64(42)); // Error: float precision insufficient
|
|
@@ -133,10 +106,15 @@ abstract class BaseExpectMatcher<T> {
|
|
|
133
106
|
*
|
|
134
107
|
* Strings are compared by value equality as with `toBe`. Non-numeric, non-string types return false.
|
|
135
108
|
*
|
|
136
|
-
*
|
|
109
|
+
* SIMD `v128` vectors are not supported, as approximate comparison requires a lane type
|
|
110
|
+
* interpretation to extract numeric values. Extract lane values as needed and compare them individually.
|
|
111
|
+
*
|
|
112
|
+
* @param precision - Specify the number of decimal places that must match for values to be
|
|
137
113
|
* considered close. Defaults to 2 digits, meaning effectively that values must be within 0.005 of
|
|
138
114
|
* each other.
|
|
139
115
|
*
|
|
116
|
+
* @throws When either value is a `v128` vector.
|
|
117
|
+
*
|
|
140
118
|
* @example
|
|
141
119
|
* expect(0.1 + 0.2).toBeCloseTo(0.3);
|
|
142
120
|
* expect(1.005).toBeCloseTo(1.0, 1);
|
|
@@ -153,11 +131,14 @@ abstract class BaseExpectMatcher<T> {
|
|
|
153
131
|
* (more permissive than AS's own `>` operator). Booleans are treated as numeric
|
|
154
132
|
* (true=1, false=0).
|
|
155
133
|
*
|
|
134
|
+
* SIMD `v128` vectors are not supported, as numeric lane-wise comparison requires a
|
|
135
|
+
* specific lane type interpretation. Extract lane values and compare them individually instead.
|
|
136
|
+
*
|
|
156
137
|
* @throws When comparing float/integer types where the float's mantissa cannot losslessly
|
|
157
138
|
* represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
|
|
158
139
|
* @throws When comparing nullable strings where either value is null. Use `toBeNull()`
|
|
159
140
|
* to check for null values.
|
|
160
|
-
* @throws When comparing non-string reference types (objects, arrays, etc).
|
|
141
|
+
* @throws When comparing non-string reference types (objects, arrays, etc) or `v128` vectors.
|
|
161
142
|
*
|
|
162
143
|
* @example
|
|
163
144
|
* expect(10).toBeGreaterThan(5);
|
|
@@ -179,11 +160,14 @@ abstract class BaseExpectMatcher<T> {
|
|
|
179
160
|
* (more permissive than AS's own `>=` operator). Booleans are treated as numeric
|
|
180
161
|
* (true=1, false=0).
|
|
181
162
|
*
|
|
163
|
+
* SIMD `v128` vectors are not supported, as numeric lane-wise comparison requires a
|
|
164
|
+
* specific lane type interpretation. Extract lane values and compare them individually instead.
|
|
165
|
+
*
|
|
182
166
|
* @throws When comparing float/integer types where the float's mantissa cannot losslessly
|
|
183
167
|
* represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
|
|
184
168
|
* @throws When comparing nullable strings where either value is null. Use `toBeNull()`
|
|
185
169
|
* to check for null values.
|
|
186
|
-
* @throws When comparing non-string reference types (objects, arrays, etc).
|
|
170
|
+
* @throws When comparing non-string reference types (objects, arrays, etc) or `v128` vectors.
|
|
187
171
|
*
|
|
188
172
|
* @example
|
|
189
173
|
* expect(10).toBeGreaterThanOrEqual(10);
|
|
@@ -204,11 +188,14 @@ abstract class BaseExpectMatcher<T> {
|
|
|
204
188
|
* (more permissive than AS's own `<` operator). Booleans are treated as numeric
|
|
205
189
|
* (true=1, false=0).
|
|
206
190
|
*
|
|
191
|
+
* SIMD `v128` vectors are not supported, as numeric lane-wise comparison requires a
|
|
192
|
+
* specific lane type interpretation. Extract lane values and compare them individually instead.
|
|
193
|
+
*
|
|
207
194
|
* @throws When comparing float/integer types where the float's mantissa cannot losslessly
|
|
208
195
|
* represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
|
|
209
196
|
* @throws When comparing nullable strings where either value is null. Use `toBeNull()`
|
|
210
197
|
* to check for null values.
|
|
211
|
-
* @throws When comparing non-string reference types (objects, arrays, etc).
|
|
198
|
+
* @throws When comparing non-string reference types (objects, arrays, etc) or `v128` vectors.
|
|
212
199
|
*
|
|
213
200
|
* @example
|
|
214
201
|
* expect(5).toBeLessThan(10);
|
|
@@ -230,11 +217,14 @@ abstract class BaseExpectMatcher<T> {
|
|
|
230
217
|
* (more permissive than AS's own `<=` operator). Booleans are treated as numeric
|
|
231
218
|
* (true=1, false=0).
|
|
232
219
|
*
|
|
220
|
+
* SIMD `v128` vectors are not supported, as numeric lane-wise comparison requires a
|
|
221
|
+
* specific lane type interpretation. Extract lane values and compare them individually instead.
|
|
222
|
+
*
|
|
233
223
|
* @throws When comparing float/integer types where the float's mantissa cannot losslessly
|
|
234
224
|
* represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
|
|
235
225
|
* @throws When comparing nullable strings where either value is null. Use `toBeNull()`
|
|
236
226
|
* to check for null values.
|
|
237
|
-
* @throws When comparing non-string reference types (objects, arrays, etc).
|
|
227
|
+
* @throws When comparing non-string reference types (objects, arrays, etc) or `v128` vectors.
|
|
238
228
|
*
|
|
239
229
|
* @example
|
|
240
230
|
* expect(5).toBeLessThanOrEqual(5);
|
|
@@ -248,42 +238,119 @@ abstract class BaseExpectMatcher<T> {
|
|
|
248
238
|
}
|
|
249
239
|
|
|
250
240
|
/**
|
|
251
|
-
* Checks that two values have the same value (deep equality).
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* for membership. Primitives, strings, and other object references are compared with
|
|
255
|
-
* `toBe()` rules.
|
|
256
|
-
*
|
|
241
|
+
* Checks that two values have the same value (deep equality). Primitives and strings
|
|
242
|
+
* are compared by value, and object references are tested for deep equality.
|
|
243
|
+
*
|
|
257
244
|
* Like `toBe`, cross-type numeric comparisons follow AssemblyScript's own `==` operator
|
|
258
245
|
* restrictions. `toBeCloseTo()` is safer for any comparison involving a float and
|
|
259
246
|
* accurately handles precision-loss edge cases.
|
|
260
|
-
*
|
|
261
|
-
*
|
|
247
|
+
*
|
|
248
|
+
* Built-in object references are compared with the following deep equality rules:
|
|
249
|
+
* - `Array`, `StaticArray`, `TypedArray`: element-by-element comparison using `toEqual()` recursively
|
|
250
|
+
* - `Set`: deep element equality (same elements, order-independent) using `toEqual()`
|
|
251
|
+
* - `Map`: key-by-key comparison using `toEqual()` on values. Key types must match
|
|
252
|
+
* exactly; value types support cross-type comparison
|
|
253
|
+
* - `ArrayBuffer`: byte-level content comparison
|
|
254
|
+
*
|
|
255
|
+
* Arrays, Sets, and Maps support cross-type comparison where element/value types are
|
|
256
|
+
* compatible (e.g. `Array<i32>` vs `Array<f64>`, `Map<string, i32>` vs `Map<string, f64>`).
|
|
257
|
+
* For Maps, key types must match exactly - only value types can differ.
|
|
258
|
+
*
|
|
259
|
+
* User object references of the same runtime type are compared using a deep field-by-field
|
|
260
|
+
* comparison of all stored instance fields using `toEqual()` recursively.
|
|
261
|
+
* - Includes public, protected, and private fields
|
|
262
|
+
* - Getters are **excluded**
|
|
263
|
+
* - User-defined `@operator("==")` or `.equals()` methods are used if present, instead
|
|
264
|
+
* of field-by-field comparison
|
|
265
|
+
* - Supports inheritance, generics, and nullable fields
|
|
266
|
+
* - Objects with different runtime types are not equal even when they share
|
|
267
|
+
* the same fields & values, making behavior the same as `toStrictEqual` in the
|
|
268
|
+
* AssemblyScript pool. This differs from vitest's JavaScript `toEqual()`,
|
|
269
|
+
* which compares structurally regardless of constructor / runtime type
|
|
270
|
+
* - Note: If a user class extends a library class (from `node_modules` or AS stdlib),
|
|
271
|
+
* only the user class's own declared fields are compared. Inherited library fields
|
|
272
|
+
* are not included, as deep equality injection is scoped to user source files only
|
|
273
|
+
*
|
|
274
|
+
* SIMD vectors use WASM's native `==` comparison, which compares at the bit level,
|
|
275
|
+
* ignoring lane type.
|
|
262
276
|
*
|
|
263
277
|
* @throws When comparing float/integer types where the float's mantissa cannot losslessly
|
|
264
278
|
* represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
|
|
279
|
+
* @throws When comparing fundamentally incompatible types: reference vs value type
|
|
280
|
+
* (e.g. `string` vs `i32`, unless one is null/zero), or `v128` vs non-vector type.
|
|
281
|
+
* @throws When comparing containers with incompatible element types (e.g. `Array<string>`
|
|
282
|
+
* vs `Array<i32>`), or precision-loss numeric combinations (e.g. `Array<f32>` vs `Array<i32>`).
|
|
265
283
|
*
|
|
266
284
|
* @example
|
|
267
285
|
* expect([1, 2, 3]).toEqual([1, 2, 3]);
|
|
268
286
|
* expect(["one", "two", "three"]).toEqual(["one", "two", "three"]);
|
|
269
287
|
*
|
|
270
|
-
* //
|
|
271
|
-
* const a
|
|
272
|
-
* const b
|
|
273
|
-
*
|
|
288
|
+
* // ArrayBuffer byte-level comparison
|
|
289
|
+
* const a = new ArrayBuffer(4);
|
|
290
|
+
* const b = new ArrayBuffer(4);
|
|
291
|
+
* store<u8>(changetype<usize>(a), 0x42);
|
|
292
|
+
* store<u8>(changetype<usize>(b), 66); // 66 decimal == 0x42 hex
|
|
293
|
+
* expect(a).toEqual(b);
|
|
294
|
+
*
|
|
295
|
+
* // SIMD vectors: different lane types with the same underlying bits are equal
|
|
296
|
+
* expect(i64x2(3, 7)).toEqual(i32x4(3, 0, 7, 0));
|
|
297
|
+
*
|
|
298
|
+
* // user-defined objects: deep equality
|
|
299
|
+
* const p1 = new Point(1, 2);
|
|
300
|
+
* const p2 = new Point(1, 2);
|
|
301
|
+
* expect(p1).toEqual(p2);
|
|
274
302
|
*/
|
|
275
303
|
toEqual<U>(val: U): void {
|
|
276
|
-
|
|
304
|
+
equalsRefPairsClear();
|
|
305
|
+
equalsPathClear();
|
|
306
|
+
equalsRtmNamesClear();
|
|
307
|
+
const result = equals(this.actual, val);
|
|
308
|
+
const path = equalsPathString();
|
|
309
|
+
const isRtm = result == __vitest_assemblyscript_EqualityResult.RuntimeTypeMismatch;
|
|
310
|
+
|
|
311
|
+
let suffix = "";
|
|
312
|
+
if (isRtm) {
|
|
313
|
+
const rtmNames = equalsRtmNamesSuffix();
|
|
314
|
+
suffix = path != "" ? " (runtime type mismatch at " + path + rtmNames + ")" : " (runtime type mismatch" + rtmNames + ")";
|
|
315
|
+
} else if (path != "") {
|
|
316
|
+
suffix = " (differs at " + path + ")";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this.assertComparison(result == __vitest_assemblyscript_EqualityResult.Equal, this.actual, val, "to deeply equal", true, true, suffix, isRtm);
|
|
277
320
|
}
|
|
278
321
|
|
|
279
322
|
/**
|
|
280
|
-
* Alias for `toEqual
|
|
323
|
+
* Alias for `toEqual`, no functional difference.
|
|
324
|
+
*
|
|
325
|
+
* In JavaScript, `toEqual` compares structurally regardless of type,
|
|
326
|
+
* while `toStrictEqual` requires the same runtime type.
|
|
327
|
+
*
|
|
328
|
+
* In AssemblyScript, `toEqual` already requires matching runtime types,
|
|
329
|
+
* which is consistent with how most testing frameworks behave for statically-typed
|
|
330
|
+
* languages without runtime reflection.
|
|
281
331
|
*
|
|
282
332
|
* @example
|
|
283
|
-
*
|
|
333
|
+
* const p1 = new Point(1, 2);
|
|
334
|
+
* const p2 = new Point(1, 2);
|
|
335
|
+
* expect(p1).toEqual(p2);
|
|
284
336
|
*/
|
|
285
337
|
toStrictEqual<U>(val: U): void {
|
|
286
|
-
|
|
338
|
+
equalsRefPairsClear();
|
|
339
|
+
equalsPathClear();
|
|
340
|
+
equalsRtmNamesClear();
|
|
341
|
+
const result = equals(this.actual, val);
|
|
342
|
+
const path = equalsPathString();
|
|
343
|
+
const isRtm = result == __vitest_assemblyscript_EqualityResult.RuntimeTypeMismatch;
|
|
344
|
+
|
|
345
|
+
let suffix = "";
|
|
346
|
+
if (isRtm) {
|
|
347
|
+
const rtmNames = equalsRtmNamesSuffix();
|
|
348
|
+
suffix = path != "" ? " (runtime type mismatch at " + path + rtmNames + ")" : " (runtime type mismatch" + rtmNames + ")";
|
|
349
|
+
} else if (path != "") {
|
|
350
|
+
suffix = " (differs at " + path + ")";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.assertComparison(result == __vitest_assemblyscript_EqualityResult.Equal, this.actual, val, "to strictly equal", true, true, suffix, isRtm);
|
|
287
354
|
}
|
|
288
355
|
|
|
289
356
|
/**
|
|
@@ -293,10 +360,13 @@ abstract class BaseExpectMatcher<T> {
|
|
|
293
360
|
* an object reference, not a primitive. An empty string is still an allocated object
|
|
294
361
|
* with a non-zero address, so it evaluates as truthy.
|
|
295
362
|
*
|
|
363
|
+
* A SIMD `v128` vector with at least one non-zero bit is truthy; an all-zero vector is falsy.
|
|
364
|
+
*
|
|
296
365
|
* @example
|
|
297
366
|
* expect(1).toBeTruthy();
|
|
298
367
|
* expect("hello").toBeTruthy();
|
|
299
368
|
* expect("").toBeTruthy(); // truthy in AS (unlike JS)
|
|
369
|
+
* expect(i32x4.splat(1)).toBeTruthy();
|
|
300
370
|
*/
|
|
301
371
|
toBeTruthy(): void {
|
|
302
372
|
this.assertComparison(truthyOrFalsey(this.actual, true), this.actual, true, "to be truthy", false);
|
|
@@ -309,11 +379,14 @@ abstract class BaseExpectMatcher<T> {
|
|
|
309
379
|
* an object reference, not a primitive. An empty string is still an allocated object
|
|
310
380
|
* with a non-zero address, so it evaluates as truthy.
|
|
311
381
|
*
|
|
382
|
+
* An all-zero SIMD `v128` vector is falsy; a vector with at least one non-zero bit is truthy.
|
|
383
|
+
*
|
|
312
384
|
* @example
|
|
313
385
|
* expect(0).toBeFalsy();
|
|
314
386
|
* expect(NaN).toBeFalsy();
|
|
315
387
|
* expect(null).toBeFalsy();
|
|
316
388
|
* expect("").not.toBeFalsy(); // not falsy in AS (unlike JS)
|
|
389
|
+
* expect(i32x4.splat(0)).toBeFalsy();
|
|
317
390
|
*/
|
|
318
391
|
toBeFalsy(): void {
|
|
319
392
|
this.assertComparison(truthyOrFalsey(this.actual, false), this.actual, false, "to be falsey", false);
|
|
@@ -340,7 +413,7 @@ abstract class BaseExpectMatcher<T> {
|
|
|
340
413
|
|
|
341
414
|
/**
|
|
342
415
|
* Checks that the type of the value is nullable (can hold `null`). This is a type-level
|
|
343
|
-
* check, not a value check
|
|
416
|
+
* check, not a value check - a bare `null` (which is `usize(0)`) is not itself a nullable type.
|
|
344
417
|
* Use `toBeNull()` to check if a value is null.
|
|
345
418
|
*
|
|
346
419
|
* @example
|
|
@@ -406,16 +479,36 @@ abstract class BaseExpectMatcher<T> {
|
|
|
406
479
|
}
|
|
407
480
|
}
|
|
408
481
|
|
|
409
|
-
protected assertComparison<U, V>(rawCondition: bool, actual: U, expected: V, methodStr: string, printExpected: bool, provideDiff: bool = true): void {
|
|
482
|
+
protected assertComparison<U, V>(rawCondition: bool, actual: U, expected: V, methodStr: string, printExpected: bool, provideDiff: bool = true, suffix: string = "", isRtm: bool = false): void {
|
|
410
483
|
const condition = this.isInverted ? !rawCondition : rawCondition;
|
|
411
484
|
|
|
412
485
|
if (condition) {
|
|
413
486
|
__assertion_pass();
|
|
414
487
|
} else {
|
|
415
488
|
const notStr = this.isInverted ? "not " : "";
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
489
|
+
// For runtime type mismatches, show the top-level type name only — the mismatched
|
|
490
|
+
// types in the suffix are the useful information, not field contents of differently-typed
|
|
491
|
+
// objects. Uses __vitest_assemblyscript_typename (virtual dispatch gives runtime name)
|
|
492
|
+
// with nameof fallback for types without injection (containers, primitives).
|
|
493
|
+
// For value mismatches, full stringification shows what values differ.
|
|
494
|
+
let actualStr: string;
|
|
495
|
+
let expectedStr: string;
|
|
496
|
+
if (isRtm) {
|
|
497
|
+
// @ts-ignore
|
|
498
|
+
actualStr = isDefined(actual.__vitest_assemblyscript_typename)
|
|
499
|
+
// @ts-ignore
|
|
500
|
+
? (<NonNullable<U>>actual).__vitest_assemblyscript_typename()
|
|
501
|
+
: nameof<U>();
|
|
502
|
+
// @ts-ignore
|
|
503
|
+
expectedStr = isDefined(expected.__vitest_assemblyscript_typename)
|
|
504
|
+
// @ts-ignore
|
|
505
|
+
? (<NonNullable<V>>expected).__vitest_assemblyscript_typename()
|
|
506
|
+
: nameof<V>();
|
|
507
|
+
} else {
|
|
508
|
+
actualStr = stringifyValue(actual);
|
|
509
|
+
expectedStr = stringifyValue(expected);
|
|
510
|
+
}
|
|
511
|
+
const msg = `expected ${actualStr} ${notStr}${methodStr}${printExpected ? ` ${expectedStr}` : ""}${suffix}`;
|
|
419
512
|
|
|
420
513
|
__assertion_fail<string>(msg, nameof<U>() + " " + nameof<V>(), provideDiff, actualStr, expectedStr);
|
|
421
514
|
|
package/assembly/index.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test framework with per-test crash isolation support
|
|
3
|
-
*
|
|
4
|
-
* Execution flow:
|
|
5
|
-
* 1. Instantiation: Pool creates WASM instance with import callbacks
|
|
6
|
-
* 2. Registration: _start() runs, top-level test() calls invoke __register_test callback
|
|
7
|
-
* 3. Discovery: Pool receives test names + function indices via callbacks
|
|
8
|
-
* 4. Execution: Pool calls table.get(fnIndex)() directly via exported function table
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
export * from './describe';
|
|
12
2
|
export * from './expect';
|
|
13
3
|
export * from './options';
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export function isNull<T>(value: T): bool {
|
|
2
|
+
if (isReference<T>()) {
|
|
3
|
+
if (isNullable<T>()) {
|
|
4
|
+
return value == null;
|
|
5
|
+
} else {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
} else {
|
|
9
|
+
if (isBoolean<T>()) {
|
|
10
|
+
return false;
|
|
11
|
+
} else if (isVector<T>()) {
|
|
12
|
+
return false;
|
|
13
|
+
} else {
|
|
14
|
+
return nameof<T>(value) == 'usize' && value == 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function nan<T>(value: T): bool {
|
|
20
|
+
if (isFloat<T>()) {
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
return isNaN<T>(value);
|
|
23
|
+
} else {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Cycle detection for stringification. Tracks which object pointers are currently
|
|
30
|
+
* being stringified to prevent infinite recursion on self-referential objects.
|
|
31
|
+
*
|
|
32
|
+
* Uses add-on-entry/remove-on-exit discipline (unlike equalsRefPairs which is add-only).
|
|
33
|
+
* This is necessary because stringifyValue is called twice per assertion (once for actual,
|
|
34
|
+
* once for expected) — the same pointer appearing in both calls is not a cycle. Add/remove
|
|
35
|
+
* ensures the set is naturally empty between calls with no external clearing needed.
|
|
36
|
+
*/
|
|
37
|
+
const stringifyVisited = new Set<usize>();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Produces a human-readable string representation of a value for assertion messages.
|
|
41
|
+
* Handles primitives, strings, arrays, Sets, Maps, ArrayBuffers, and user-defined objects.
|
|
42
|
+
* Used by matchers to format actual/expected values in error output and diffs.
|
|
43
|
+
*/
|
|
44
|
+
export function stringifyValue<T>(item: T): string {
|
|
45
|
+
if (isNull(item)) return "null";
|
|
46
|
+
if (nan(item)) return "NaN";
|
|
47
|
+
|
|
48
|
+
if (isReference<T>()) {
|
|
49
|
+
if (isString<T>()) return `"${item}"`;
|
|
50
|
+
|
|
51
|
+
// Cycle detection for all non-string reference types before recursive stringification
|
|
52
|
+
const ptr = changetype<usize>(item);
|
|
53
|
+
if (stringifyVisited.has(ptr)) return "[Circular]";
|
|
54
|
+
stringifyVisited.add(ptr);
|
|
55
|
+
|
|
56
|
+
let result: string;
|
|
57
|
+
if (item instanceof ArrayBuffer) {
|
|
58
|
+
result = `ArrayBuffer[${item.byteLength}]`;
|
|
59
|
+
} else if (isArrayLike<T>(item)) {
|
|
60
|
+
result = arrayMessageString(item);
|
|
61
|
+
} else if (item instanceof Set) {
|
|
62
|
+
result = setMessageString(item);
|
|
63
|
+
} else if (item instanceof Map) {
|
|
64
|
+
result = mapMessageString(item);
|
|
65
|
+
// @ts-ignore: __vitest_assemblyscript_typename is injected by the compiler transform
|
|
66
|
+
} else if (isDefined(item.__vitest_assemblyscript_typename)) {
|
|
67
|
+
// Cast to NonNullable<T> because AS doesn't narrow nullability from the earlier
|
|
68
|
+
// null check — it requires explicit type narrowing to call methods on nullable types.
|
|
69
|
+
// Safe because null case returns "null" at the top of this function.
|
|
70
|
+
const nonNullItem = <NonNullable<T>>item;
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
const typeName: string = nonNullItem.__vitest_assemblyscript_typename();
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
if (isDefined(nonNullItem.__vitest_assemblyscript_stringify)) {
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
const fields: string = nonNullItem.__vitest_assemblyscript_stringify();
|
|
77
|
+
result = fields != "" ? typeName + " { " + fields + " }" : typeName;
|
|
78
|
+
} else {
|
|
79
|
+
result = typeName;
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
result = nameof<T>(item);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
stringifyVisited.delete(ptr);
|
|
86
|
+
return result;
|
|
87
|
+
} else if (isBoolean<T>()){
|
|
88
|
+
return bool(item).toString();
|
|
89
|
+
} else if (isInteger<T>(item) || isFloat<T>(item)) {
|
|
90
|
+
return item.toString();
|
|
91
|
+
} else {
|
|
92
|
+
return nameof<T>(item);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Global bridge for the compiler transform's injected stringify methods.
|
|
98
|
+
*
|
|
99
|
+
* Transform-injected __vitest_assemblyscript_stringify methods call this to format each
|
|
100
|
+
* field value. Declared global to make it available in all source files without import,
|
|
101
|
+
* solving the afterParse import resolution limitation.
|
|
102
|
+
*
|
|
103
|
+
* Loaded into the compilation transitively: user test imports
|
|
104
|
+
* vitest-pool-assemblyscript/assembly → index.ts → expect.ts → compare.ts → utils.ts.
|
|
105
|
+
*/
|
|
106
|
+
// @ts-ignore: AS-specific global decorator
|
|
107
|
+
@global
|
|
108
|
+
function __vitest_assemblyscript_stringify_value<T>(item: T): string {
|
|
109
|
+
return stringifyValue<T>(item);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function arrayMessageString<T extends ArrayLike<unknown>>(array: T): string {
|
|
113
|
+
if (isNullable<T>(array) && array == null) {
|
|
114
|
+
return "null";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let str = "[";
|
|
118
|
+
for (let i = 0; i < array.length; i++) {
|
|
119
|
+
str += stringifyValue(array[i]);
|
|
120
|
+
|
|
121
|
+
if (i < array.length - 1) {
|
|
122
|
+
str += ","
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
str += "]";
|
|
126
|
+
|
|
127
|
+
return str;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setMessageString<T>(set: T): string {
|
|
131
|
+
if (isNullable<T>(set) && set == null) {
|
|
132
|
+
return "null";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (set instanceof Set) {
|
|
136
|
+
const values = set.values();
|
|
137
|
+
let str = "Set {";
|
|
138
|
+
for (let i = 0; i < values.length; i++) {
|
|
139
|
+
if (i > 0) str += ",";
|
|
140
|
+
str += " " + stringifyValue(values[i]);
|
|
141
|
+
}
|
|
142
|
+
if (values.length > 0) str += " ";
|
|
143
|
+
str += "}";
|
|
144
|
+
return str;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return nameof<T>(set);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mapMessageString<T>(map: T): string {
|
|
151
|
+
if (isNullable<T>(map) && map == null) {
|
|
152
|
+
return "null";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (map instanceof Map) {
|
|
156
|
+
const keys = map.keys();
|
|
157
|
+
let str = "Map {";
|
|
158
|
+
for (let i = 0; i < keys.length; i++) {
|
|
159
|
+
if (i > 0) str += ",";
|
|
160
|
+
str += " " + stringifyValue(keys[i]) + " => " + stringifyValue(map.get(keys[i]));
|
|
161
|
+
}
|
|
162
|
+
if (keys.length > 0) str += " ";
|
|
163
|
+
str += "}";
|
|
164
|
+
return str;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return nameof<T>(map);
|
|
168
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { INTERNAL_PATH_LIB_PREFIX, POOL_ERROR_NAMES } from "./constants-
|
|
2
|
-
import { createPoolError, debug } from "./debug-
|
|
1
|
+
import { INTERNAL_PATH_LIB_PREFIX, POOL_ERROR_NAMES } from "./constants-DbxJ3hzg.mjs";
|
|
2
|
+
import { createPoolError, debug } from "./debug-DtRAL4rM.mjs";
|
|
3
3
|
import { toForwardSlash } from "./path-utils-t9OzjXYF.mjs";
|
|
4
4
|
import { getShortFunctionName } from "./wasm-names-BFtzQCH4.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -184,6 +184,7 @@ const instrumentForCoverage = (wasmBuffer, sourceMapBuffer, instrumentationOptio
|
|
|
184
184
|
excludedFiles: instrumentationOptions.relativeExcludedFiles,
|
|
185
185
|
excludedLibraryFilePrefix: instrumentationOptions.excludedLibraryFilePrefix,
|
|
186
186
|
excludedLibraryFileOverridePrefix: instrumentationOptions.excludedLibraryFileOverridePrefix,
|
|
187
|
+
excludedInternalFunctionSubstring: instrumentationOptions.excludedInternalFunctionSubstring,
|
|
187
188
|
debug: instrumentationOptions.debug,
|
|
188
189
|
logPrefix: nativeLogPrefix
|
|
189
190
|
};
|
|
@@ -203,4 +204,4 @@ const instrumentForCoverage = (wasmBuffer, sourceMapBuffer, instrumentationOptio
|
|
|
203
204
|
|
|
204
205
|
//#endregion
|
|
205
206
|
export { instrumentForCoverage };
|
|
206
|
-
//# sourceMappingURL=addon-interface-
|
|
207
|
+
//# sourceMappingURL=addon-interface-CYFXMbK7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addon-interface-CYFXMbK7.mjs","names":[],"sources":["../src/instrumentation/addon-interface.ts"],"sourcesContent":["/**\n * Native addon interface for extracting debug information from WebAssembly binaries\n *\n * This module wraps Binaryen's C++ API to provide expression-level debug locations\n * and basic block information that the JavaScript API doesn't expose.\n *\n * The native addon outputs raw data (0-based columns, relative paths) which this\n * wrapper transforms into the final BinaryDebugInfo format (1-based columns,\n * absolute paths, grouped by file and position).\n */\n\nimport { createRequire } from 'node:module';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type {\n NativeAddon,\n NativeInstrumentationResult,\n NativeDebugInfoOutput,\n NativeFunctionDebugInfo,\n NativeExpressionDebugInfo,\n NativeSourceLocation,\n BinaryDebugInfo,\n FunctionDebugInfo,\n SourceLocation,\n ExpressionDebugInfo,\n InstrumentationResult,\n NativeInstrumentationOptions,\n InstrumentationOptions,\n InstrumentForCoverageFunc,\n} from '../types/types.js';\nimport {\n POOL_ERROR_NAMES,\n INTERNAL_PATH_LIB_PREFIX,\n} from '../types/constants.js';\nimport { debug } from '../util/debug.js';\nimport { toForwardSlash } from '../util/path-utils.js';\nimport { createPoolError } from '../util/pool-errors.js';\nimport { getShortFunctionName } from '../wasm-executor/wasm-names.js';\n\n// Load the native addon via node-gyp-build\n// node-gyp-build checks: prebuilds/ first, then build/Release/\n// It searches from the given directory for a package.json to find the package root.\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst rootFromDist = resolve(__dirname, '..');\n// TODO: Verify if this src fallback is still needed — pool always runs from dist/ even locally.\n// If confirmed dead code, remove this and simplify to a single nodeGypBuild call.\nconst rootFromSrc = resolve(__dirname, '../..');\n\nconst require = createRequire(import.meta.url);\nconst nodeGypBuild: (dir: string) => NativeAddon = require('node-gyp-build');\n\nlet addon: NativeAddon;\ntry {\n addon = nodeGypBuild(rootFromDist);\n} catch {\n try {\n addon = nodeGypBuild(rootFromSrc);\n } catch (err) {\n throw createPoolError(\n `vitest-pool-assemblyscript native instrumentation addon not found. Searched from ${rootFromDist} and ${rootFromSrc}. `\n + `Ensure prebuilds are available or run 'node node_modules/vitest-pool-assemblyscript/scripts/install-native-addon.js'`\n + ` to compile from source. Original error: ${err instanceof Error ? err.message : String(err)}`,\n POOL_ERROR_NAMES.WASMInstrumentationError\n );\n }\n}\n\n/**\n * Convert a raw location (0-indexed columns, path indexes) to\n * processed location (1-indexed columns, absolute path strings)\n *\n * Source map paths vary by import style and project structure:\n * - Relative imports: \"assembly/compare.ts\"\n * - Bare package imports: \"~lib/vitest-pool-assemblyscript/assembly/compare.ts\"\n * - Source outside project root: \"../assembly/compare.ts\" (e.g. test-external importing parent sources)\n *\n * All are normalized to absolute filesystem paths for consistent coverage key matching.\n */\nfunction convertLocation(\n rawLocation: NativeSourceLocation,\n debugSourceFiles: string[],\n projectRoot: string\n): SourceLocation {\n let filePath = debugSourceFiles[rawLocation.fileIndex];\n\n if (!filePath) {\n throw createPoolError(\n `No debug source file with index: ${rawLocation.fileIndex}}`,\n POOL_ERROR_NAMES.WASMInstrumentationError\n );\n }\n\n // Normalize to absolute path for consistent coverage key matching\n if (filePath.startsWith(INTERNAL_PATH_LIB_PREFIX)) {\n // ~lib/vitest-pool-assemblyscript/assembly/X.ts -> projectRoot/assembly/X.ts\n const relativePart = filePath.slice(INTERNAL_PATH_LIB_PREFIX.length);\n filePath = toForwardSlash(resolve(projectRoot, 'assembly', relativePart));\n } else {\n // Resolve relative path (handles both 'assembly/X.ts' and '../assembly/X.ts')\n filePath = toForwardSlash(resolve(projectRoot, filePath));\n }\n\n return {\n filePath,\n line: rawLocation.line,\n column: rawLocation.column + 1, // convert from 0-indexed to 1-indexed\n };\n}\n\n/**\n * Convert a raw expression to processed format\n */\nfunction convertExpression(\n rawExpr: NativeExpressionDebugInfo,\n debugSourceFiles: string[],\n projectRoot: string\n): ExpressionDebugInfo {\n const converted: ExpressionDebugInfo = {\n type: rawExpr.type,\n isBranch: rawExpr.isBranch,\n };\n\n if (rawExpr.branchPaths !== undefined) {\n converted.branchPaths = rawExpr.branchPaths;\n }\n\n if (rawExpr.location) {\n const convertedLocation = convertLocation(rawExpr.location, debugSourceFiles, projectRoot);\n if (convertedLocation) {\n converted.location = convertedLocation;\n }\n }\n\n return converted;\n}\n\n/**\n * Generate a position key to identify the SourceLocation uniquely\n * within a file. Does NOT include the file identifier.\n */\nfunction getPositionKey(location: SourceLocation) {\n return `${location.line}:${location.column}`;\n}\n\n/**\n * Convert a raw function to processed format and compute position key\n * Returns undefined if function has no valid representative location\n */\nfunction convertFunction(\n rawFunc: NativeFunctionDebugInfo,\n debugSourceFiles: string[],\n projectRoot: string\n): { func: FunctionDebugInfo; filePath: string; positionKey: string } | undefined {\n const representativeLocation = convertLocation(rawFunc.representativeLocation, debugSourceFiles, projectRoot);\n\n // Convert expressions\n const expressions: ExpressionDebugInfo[] = [];\n if (rawFunc.expressions) {\n for (const expr of rawFunc.expressions) {\n expressions.push(convertExpression(expr, debugSourceFiles, projectRoot));\n }\n }\n\n const converted: FunctionDebugInfo = {\n wasmIndex: rawFunc.wasmIndex,\n name: rawFunc.name,\n representativeLocation,\n coverageMemoryIndex: rawFunc.coverageMemoryIndex,\n expressions,\n basicBlocks: rawFunc.basicBlocks,\n };\n\n const filePath = representativeLocation.filePath;\n const positionKey = getPositionKey(representativeLocation);\n\n return { func: converted, filePath, positionKey };\n}\n\n/**\n * Check if two WASM function names are monomorphizations of the same generic function.\n * Generic monomorphizations share the same base name and suffix, differing only in\n * the type parameters inside angle brackets.\n *\n * e.g. \"assembly/compare/closeTo<bool\\2cbool>\" and \"assembly/compare/closeTo<bool\\2cu8>\"\n * e.g. \"assembly/expect/BaseExpectMatcher<bool>#constructor\" and \"assembly/expect/BaseExpectMatcher<i8>#constructor\"\n */\nfunction isGenericMonomorphizationMatch(nameA: string, nameB: string): boolean {\n const openA = nameA.indexOf('<');\n const openB = nameB.indexOf('<');\n\n // Both must contain generic type parameters\n if (openA === -1 || openB === -1) return false;\n\n const lastCloseA = nameA.lastIndexOf('>');\n const lastCloseB = nameB.lastIndexOf('>');\n\n if (lastCloseA === -1 || lastCloseB === -1) return false;\n\n // Prefix before '<' must match (includes module path, class, function name)\n const prefixA = nameA.substring(0, openA);\n const prefixB = nameB.substring(0, openB);\n if (prefixA !== prefixB) return false;\n\n // Suffix after last '>' must match (e.g. \"#constructor\", or empty)\n const suffixA = nameA.substring(lastCloseA + 1);\n const suffixB = nameB.substring(lastCloseB + 1);\n if (suffixA !== suffixB) return false;\n\n return true;\n}\n\n/**\n * Transform raw native addon output to processed BinaryDebugInfo\n */\nfunction transformDebugInfo(\n raw: NativeDebugInfoOutput,\n logPrefix: string,\n projectRoot: string,\n): BinaryDebugInfo {\n const functionsByFileAndPosition: Record<string, Record<string, FunctionDebugInfo[]>> = {};\n\n debug(`${logPrefix} - Converting ${raw.functions.length} functions`);\n\n let genericCollisionCount = 0;\n let skippedCount = 0;\n let instrumentedFunctionCount = 0;\n\n for (const rawFunc of raw.functions) {\n const result = convertFunction(rawFunc, raw.debugSourceFiles, projectRoot);\n if (!result) {\n debug(`${logPrefix} - WARNING: Skipped function (bad conversion): \"${rawFunc.name}\"`);\n skippedCount++;\n continue;\n }\n\n const { func, filePath, positionKey } = result;\n\n // Check for position collisions\n const existingAtPosition = functionsByFileAndPosition[filePath]?.[positionKey];\n if (existingAtPosition) {\n const existingName = existingAtPosition[0]!.name;\n\n // Only allow collision if both are monomorphizations of the same generic\n if (isGenericMonomorphizationMatch(existingName, func.name)) {\n existingAtPosition.push(func);\n genericCollisionCount++;\n instrumentedFunctionCount++;\n debug(\n `${logPrefix} - Generic monomorphization at ${filePath}:${positionKey}:`\n + ` \"${getShortFunctionName(func.name)}\" grouped with \"${getShortFunctionName(existingName)}\"`\n );\n continue;\n }\n\n throw createPoolError(\n `ERROR - Function Debug Position Collision at ${filePath}:${positionKey}: \"${getShortFunctionName(existingName)}\"`\n + ` will be replaced by \"${getShortFunctionName(func.name)}\". This is a bug. Please report it at:`\n + ` https://github.com/themattspiral/vitest-pool-assemblyscript/issues/new`,\n POOL_ERROR_NAMES.WASMInstrumentationError\n );\n }\n\n instrumentedFunctionCount++;\n\n // Group by file and position\n if (!functionsByFileAndPosition[filePath]) {\n functionsByFileAndPosition[filePath] = {};\n }\n\n functionsByFileAndPosition[filePath][positionKey] = [func];\n }\n\n debug(\n `${logPrefix} - BinaryDebugInfo transform complete: ${instrumentedFunctionCount} instrumented functions`\n + ` (${genericCollisionCount} generic collisions, ${skippedCount} skipped)`\n );\n\n return {\n debugSourceFiles: raw.debugSourceFiles,\n functionsByFileAndPosition,\n instrumentedFunctionCount,\n };\n}\n\n/**\n * Instrument a WASM binary for coverage collection and regenerate source map\n *\n * This function:\n * 1. Adds __coverage_memory import (multi-memory for coverage counters)\n * 2. Injects coverage counter increments at each function entry\n * 3. Regenerates source map with correct offsets after instrumentation\n * 4. Extracts debug info with coverageMemoryIndex assigned\n *\n * @param wasmBuffer - Buffer containing the clean WASM binary\n * @param sourceMapBuffer - Buffer containing the source map JSON\n * @returns Instrumented binary, regenerated source map, and debug info\n *\n * @throws {TypeError} If wasmBuffer or sourceMapBuffer are not Buffers\n * @throws {Error} If WASM binary or source map is invalid\n */\nexport const instrumentForCoverage: InstrumentForCoverageFunc = (\n wasmBuffer: Buffer,\n sourceMapBuffer: Buffer,\n instrumentationOptions: InstrumentationOptions,\n logModule: string,\n logLabel: string,\n): InstrumentationResult => {\n if (!Buffer.isBuffer(wasmBuffer)) {\n throw createPoolError(\n 'instrumentForCoverage - wasmBuffer must be a Buffer',\n POOL_ERROR_NAMES.WASMInstrumentationError\n );\n }\n if (!Buffer.isBuffer(sourceMapBuffer)) {\n throw createPoolError(\n 'instrumentForCoverage - sourceMapBuffer must be a Buffer',\n POOL_ERROR_NAMES.WASMInstrumentationError\n );\n }\n\n const interfaceLogPrefix = `[${logModule} Inst] ${logLabel}`;\n const nativeLogPrefix = `[${logModule} InstNative] ${logLabel}`;\n\n debug(`${interfaceLogPrefix} - Calling native instrumentForCoverage`);\n const startTime = performance.now();\n\n const options: NativeInstrumentationOptions = {\n coverageMemoryPagesMin: instrumentationOptions.coverageMemoryPagesMin,\n coverageMemoryPagesMax: instrumentationOptions.coverageMemoryPagesMax,\n excludedFiles: instrumentationOptions.relativeExcludedFiles,\n excludedLibraryFilePrefix: instrumentationOptions.excludedLibraryFilePrefix,\n excludedLibraryFileOverridePrefix: instrumentationOptions.excludedLibraryFileOverridePrefix,\n excludedInternalFunctionSubstring: instrumentationOptions.excludedInternalFunctionSubstring,\n debug: instrumentationOptions.debug,\n logPrefix: nativeLogPrefix\n };\n const nativeResult: NativeInstrumentationResult = addon.instrumentForCoverage(wasmBuffer, sourceMapBuffer, options);\n const addonTime = performance.now();\n debug(`${interfaceLogPrefix} - TIMING Native addon: ${(addonTime - startTime).toFixed(2)} ms`);\n\n if (nativeResult.errors?.length) {\n throw createPoolError(\n `Errors encountered duriing native instrumentation: ${nativeResult.errors.join('\\n')}`,\n POOL_ERROR_NAMES.WASMInstrumentationError,\n );\n }\n\n const debugInfo = transformDebugInfo(nativeResult.debugInfo, interfaceLogPrefix, instrumentationOptions.projectRoot);\n \n const transformTime = performance.now();\n debug(`${interfaceLogPrefix} - TIMING DebugInfo Transform: ${(transformTime - addonTime).toFixed(2)} ms`);\n debug(`${interfaceLogPrefix} - Binary size: ${nativeResult.instrumentedWasm.length} bytes | Source map size: ${nativeResult.sourceMap.length * 2} bytes`);\n\n return {\n instrumentedWasm: nativeResult.instrumentedWasm,\n sourceMap: nativeResult.sourceMap,\n debugInfo,\n };\n}\n"],"mappings":"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"}
|