vitest-pool-assemblyscript 0.8.0 → 0.9.1
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 -1
- package/README.md +10 -13
- package/assembly/compare.ts +453 -95
- package/assembly/expect.ts +113 -62
- package/assembly/index.ts +0 -10
- package/assembly/utils.ts +168 -0
- package/binding.gyp +4 -4
- 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-BNFHRGZO.mjs} +8 -7
- package/dist/compile-runner-BNFHRGZO.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-Dqs-qd3I.mjs} +18 -8
- package/dist/compiler-Dqs-qd3I.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/compare.ts
CHANGED
|
@@ -1,44 +1,298 @@
|
|
|
1
|
-
|
|
1
|
+
import { stringifyValue } from './utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Byte offset from an object pointer to the rtId field in the AS managed object header.
|
|
5
|
+
* Every managed object has a 20-byte header preceding the payload; rtId is a u32 at offset -8.
|
|
6
|
+
* See: https://www.assemblyscript.org/runtime.html#memory-layout
|
|
7
|
+
*/
|
|
8
|
+
const MANAGED_OBJECT_RTID_BYTE_OFFSET: usize = 8;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cycle detection for deep equality comparisons. Tracks which (actual, expected) reference
|
|
12
|
+
* pairs are currently being compared to prevent infinite recursion on self-referential or
|
|
13
|
+
* mutually-referential object graphs.
|
|
14
|
+
*
|
|
15
|
+
* Pairs are packed as u64 keys: (u64(actualPtr) << 32) | u64(expectedPtr).
|
|
16
|
+
* Entries are added when a reference comparison starts and never individually removed —
|
|
17
|
+
* if a pair was previously Equal, revisiting returns Equal (correct); if NotEqual, we
|
|
18
|
+
* already returned and won't revisit. Cleared at the start of each toEqual/toStrictEqual call.
|
|
19
|
+
*/
|
|
20
|
+
const equalsRefPairs = new Set<u64>();
|
|
21
|
+
|
|
22
|
+
function equalsRefPairSeen(actualPtr: usize, expectedPtr: usize): bool {
|
|
23
|
+
const key: u64 = (u64(actualPtr) << 32) | u64(expectedPtr);
|
|
24
|
+
return equalsRefPairs.has(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function equalsRefPairMark(actualPtr: usize, expectedPtr: usize): void {
|
|
28
|
+
const key: u64 = (u64(actualPtr) << 32) | u64(expectedPtr);
|
|
29
|
+
equalsRefPairs.add(key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function equalsRefPairsClear(): void {
|
|
33
|
+
equalsRefPairs.clear();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Comparison path tracking for deep equality. Accumulates path segments (e.g. "[0]",
|
|
38
|
+
* "['key']", "{Set}") as equals() recurses into containers, building a path like
|
|
39
|
+
* "[2].name" from root to the mismatch point.
|
|
40
|
+
*
|
|
41
|
+
* Uses push/pop discipline: pop only on Equal, return-without-pop on non-Equal.
|
|
42
|
+
* As non-Equal propagates up the call stack, the path naturally accumulates to the
|
|
43
|
+
* deepest mismatch point. Cleared at the start of each toEqual/toStrictEqual call.
|
|
44
|
+
*/
|
|
45
|
+
const equalsPath: string[] = [];
|
|
46
|
+
|
|
47
|
+
function equalsPathPush(segment: string): void {
|
|
48
|
+
equalsPath.push(segment);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function equalsPathPop(): void {
|
|
52
|
+
equalsPath.pop();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function equalsPathString(): string {
|
|
56
|
+
let result = "";
|
|
57
|
+
for (let i = 0; i < equalsPath.length; i++) {
|
|
58
|
+
result += equalsPath[i];
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function equalsPathClear(): void {
|
|
64
|
+
equalsPath.length = 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function equalsPathLength(): i32 {
|
|
68
|
+
return equalsPath.length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Runtime type mismatch name tracking. When equals() detects a runtime type mismatch
|
|
73
|
+
* (different rtIds on managed objects), it captures the actual and expected runtime type
|
|
74
|
+
* names via the transform-injected __vitest_assemblyscript_typename method. These are
|
|
75
|
+
* read by toEqual/toStrictEqual to include type names in the assertion suffix
|
|
76
|
+
* (e.g. "runtime type mismatch: Circle vs Square").
|
|
77
|
+
*
|
|
78
|
+
* Cleared at the start of each toEqual/toStrictEqual call alongside path and visited set.
|
|
79
|
+
*/
|
|
80
|
+
let equalsRtmActualName: string = "";
|
|
81
|
+
let equalsRtmExpectedName: string = "";
|
|
82
|
+
|
|
83
|
+
export function equalsRtmNamesSuffix(): string {
|
|
84
|
+
if (equalsRtmActualName != "" && equalsRtmExpectedName != "") {
|
|
85
|
+
return ": " + equalsRtmActualName + " vs " + equalsRtmExpectedName;
|
|
86
|
+
}
|
|
87
|
+
return "";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function equalsRtmNamesClear(): void {
|
|
91
|
+
equalsRtmActualName = "";
|
|
92
|
+
equalsRtmExpectedName = "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Global bridge for the deep-equals compiler transform — push a path segment.
|
|
97
|
+
*
|
|
98
|
+
* Transform-injected deep equality methods call this to record which field is
|
|
99
|
+
* being compared, enabling path context like ".shape" or ".members" in error messages.
|
|
100
|
+
* Declared global to make it available in all source files without import.
|
|
101
|
+
*/
|
|
102
|
+
// @ts-ignore: AS-specific global decorator
|
|
103
|
+
@global
|
|
104
|
+
function __vitest_assemblyscript_equals_path_push(segment: string): void {
|
|
105
|
+
equalsPathPush(segment);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Global bridge for the deep-equals compiler transform — pop a path segment.
|
|
110
|
+
*
|
|
111
|
+
* Called only when a field comparison returns Equal (push/pop discipline).
|
|
112
|
+
* On non-Equal, the segment is left on the stack so the path accumulates
|
|
113
|
+
* to the deepest mismatch point.
|
|
114
|
+
*/
|
|
115
|
+
// @ts-ignore: AS-specific global decorator
|
|
116
|
+
@global
|
|
117
|
+
function __vitest_assemblyscript_equals_path_pop(): void {
|
|
118
|
+
equalsPathPop();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Returns " at <path>" or " within <path>" if the path is non-empty, otherwise empty string. */
|
|
122
|
+
function equalsPathAtSuffix(): string {
|
|
123
|
+
if (equalsPath.length == 0) return "";
|
|
124
|
+
|
|
125
|
+
// Scan for "Set" anywhere in the path stack. Set elements have no meaningful
|
|
126
|
+
// identifier, so segments pushed after "Set" (e.g. array indices from inner
|
|
127
|
+
// comparisons during the set scan) are discarded — they represent aborted
|
|
128
|
+
// comparison attempts whose segments couldn't be cleaned up because a throw
|
|
129
|
+
// halted execution. Build the path only up to and including "Set".
|
|
130
|
+
for (let i = equalsPath.length - 1; i >= 0; i--) {
|
|
131
|
+
if (equalsPath[i] == "Set") {
|
|
132
|
+
let path = "";
|
|
133
|
+
for (let j = 0; j <= i; j++) {
|
|
134
|
+
path += equalsPath[j];
|
|
135
|
+
}
|
|
136
|
+
return " within " + path;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return " at " + equalsPathString();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Result of a deep equality comparison.
|
|
145
|
+
* Declared global so it is available in all source files without import —
|
|
146
|
+
* including transform-injected deep equality methods in user classes.
|
|
147
|
+
*/
|
|
148
|
+
// @ts-ignore: AS-specific global decorator
|
|
149
|
+
@global
|
|
150
|
+
export enum __vitest_assemblyscript_EqualityResult {
|
|
151
|
+
Equal,
|
|
152
|
+
NotEqual,
|
|
153
|
+
RuntimeTypeMismatch,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function arrayEquals<T extends ArrayLike<unknown>, U extends ArrayLike<unknown>>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
2
157
|
if (actual.length != expected.length) {
|
|
3
|
-
return
|
|
158
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
4
159
|
}
|
|
5
160
|
|
|
6
161
|
for (let i = 0; i < expected.length; i++) {
|
|
7
|
-
|
|
8
|
-
|
|
162
|
+
// Context-aware format: "[N]" composes with field paths (e.g. ".members[2]"),
|
|
163
|
+
// "index [N]" reads well standalone (e.g. "differs at index [2]")
|
|
164
|
+
const segment = equalsPathLength() > 0
|
|
165
|
+
? "[" + i.toString() + "]"
|
|
166
|
+
: "index [" + i.toString() + "]";
|
|
167
|
+
equalsPathPush(segment);
|
|
168
|
+
const result = equals(actual[i], expected[i]);
|
|
169
|
+
if (result != __vitest_assemblyscript_EqualityResult.Equal) {
|
|
170
|
+
return result;
|
|
9
171
|
}
|
|
172
|
+
equalsPathPop();
|
|
10
173
|
}
|
|
11
174
|
|
|
12
|
-
return
|
|
175
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
13
176
|
}
|
|
14
177
|
|
|
15
|
-
function setEquals<T, U>(actual: T, expected: U):
|
|
178
|
+
function setEquals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
16
179
|
if (actual instanceof Set && expected instanceof Set) {
|
|
180
|
+
// Exception to push/pop discipline: always pop before returning, regardless of result.
|
|
181
|
+
// Set elements have no meaningful identifier (no index, no key), so "Set" is only
|
|
182
|
+
// useful as a terminal path segment — it should not compose with deeper segments
|
|
183
|
+
// from recursive comparisons inside elements. equalsPathAtSuffix() formats this
|
|
184
|
+
// as "within Set" instead of "at Set".
|
|
185
|
+
equalsPathPush("Set");
|
|
186
|
+
|
|
17
187
|
if (actual.size != expected.size) {
|
|
18
|
-
|
|
188
|
+
equalsPathPop();
|
|
189
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
19
190
|
}
|
|
20
191
|
|
|
192
|
+
const actualValues = actual.values();
|
|
21
193
|
const expectedValues = expected.values();
|
|
22
194
|
|
|
195
|
+
// Track which actual elements have been matched to prevent double-counting.
|
|
196
|
+
// Without this, two expected elements could both match the same actual element.
|
|
197
|
+
const matched = new Array<bool>(actualValues.length);
|
|
198
|
+
for (let i = 0; i < matched.length; i++) {
|
|
199
|
+
matched[i] = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
23
202
|
for (let i = 0; i < expectedValues.length; i++) {
|
|
24
|
-
|
|
25
|
-
|
|
203
|
+
let found = false;
|
|
204
|
+
for (let j = 0; j < actualValues.length; j++) {
|
|
205
|
+
if (!matched[j]) {
|
|
206
|
+
// Save path stack depth before each scan attempt. Failed comparisons
|
|
207
|
+
// leave stale segments (e.g. ".x" from a Point field) that would corrupt
|
|
208
|
+
// the pop discipline — restoring ensures "Set" remains the top segment.
|
|
209
|
+
const pathDepth = equalsPathLength();
|
|
210
|
+
if (equals(actualValues[j], expectedValues[i]) == __vitest_assemblyscript_EqualityResult.Equal) {
|
|
211
|
+
matched[j] = true;
|
|
212
|
+
found = true;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
equalsPath.length = pathDepth;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!found) {
|
|
219
|
+
equalsPathPop();
|
|
220
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
26
221
|
}
|
|
27
222
|
}
|
|
28
223
|
|
|
29
|
-
|
|
224
|
+
equalsPathPop();
|
|
225
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
30
226
|
}
|
|
31
227
|
|
|
32
|
-
return
|
|
228
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function mapEquals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
232
|
+
if (actual instanceof Map && expected instanceof Map) {
|
|
233
|
+
// Key types must match exactly — cross-type key comparison is not safe because
|
|
234
|
+
// .has() and .get() depend on the key's hash and equality semantics, which differ
|
|
235
|
+
// across types (e.g. string vs i32 keys have incompatible hash/lookup behavior).
|
|
236
|
+
if (nameof<indexof<T>>() != nameof<indexof<U>>()) {
|
|
237
|
+
throw new Error("Map key types must match for deep equality comparison: "
|
|
238
|
+
+ nameof<T>() + " and " + nameof<U>()
|
|
239
|
+
+ equalsPathAtSuffix()
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (actual.size != expected.size) {
|
|
244
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Cast actual to use expected's key type (verified equal above) while preserving
|
|
248
|
+
// actual's native value type. This lets us iterate expected's keys and look them
|
|
249
|
+
// up in actual, while equals() handles cross-type value comparison naturally
|
|
250
|
+
// (e.g. valueof<T>=i32 vs valueof<U>=f64).
|
|
251
|
+
const castActual = changetype<Map<indexof<U>, valueof<T>>>(actual);
|
|
252
|
+
|
|
253
|
+
// instanceof needed after changetype for the compiler to resolve Map methods
|
|
254
|
+
if (castActual instanceof Map) {
|
|
255
|
+
const expectedKeys = expected.keys();
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < expectedKeys.length; i++) {
|
|
258
|
+
const key = expectedKeys[i];
|
|
259
|
+
// Context-aware format: "[key]" composes with field paths (e.g. ".registry[\"x\"]"),
|
|
260
|
+
// "key [key]" reads well standalone (e.g. "differs at key [\"x\"]")
|
|
261
|
+
const segment = equalsPathLength() > 0
|
|
262
|
+
? "[" + stringifyValue(key) + "]"
|
|
263
|
+
: "key [" + stringifyValue(key) + "]";
|
|
264
|
+
equalsPathPush(segment);
|
|
265
|
+
|
|
266
|
+
if (!castActual.has(key)) {
|
|
267
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Cross-type value comparison delegates to equals(), which handles
|
|
271
|
+
// compatible numeric types, incomparable types, and precision-loss cases
|
|
272
|
+
const result = equals(castActual.get(key), expected.get(key));
|
|
273
|
+
if (result != __vitest_assemblyscript_EqualityResult.Equal) {
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
equalsPathPop();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
280
|
+
} else {
|
|
281
|
+
// will never happen — changetype preserves the underlying Map instance
|
|
282
|
+
unreachable();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
33
287
|
}
|
|
34
288
|
|
|
35
|
-
function arrayBufferEquals<T, U>(actual: T, expected: U):
|
|
289
|
+
function arrayBufferEquals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
36
290
|
if (!(actual instanceof ArrayBuffer) || !(expected instanceof ArrayBuffer)) {
|
|
37
|
-
return
|
|
291
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
38
292
|
}
|
|
39
293
|
|
|
40
294
|
if (actual.byteLength != expected.byteLength) {
|
|
41
|
-
return
|
|
295
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
42
296
|
}
|
|
43
297
|
|
|
44
298
|
const actualPtr = changetype<usize>(actual);
|
|
@@ -49,7 +303,7 @@ function arrayBufferEquals<T, U>(actual: T, expected: U): bool {
|
|
|
49
303
|
// compare 8 bytes at a time (u64 word-sized comparison)
|
|
50
304
|
for (let i: usize = 0; i < wordCount; i++) {
|
|
51
305
|
if (load<u64>(actualPtr + i * 8) != load<u64>(expectedPtr + i * 8)) {
|
|
52
|
-
return
|
|
306
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
53
307
|
}
|
|
54
308
|
}
|
|
55
309
|
|
|
@@ -57,37 +311,11 @@ function arrayBufferEquals<T, U>(actual: T, expected: U): bool {
|
|
|
57
311
|
const remainderOffset = wordCount * 8;
|
|
58
312
|
for (let i: usize = 0; i < remainder; i++) {
|
|
59
313
|
if (load<u8>(actualPtr + remainderOffset + i) != load<u8>(expectedPtr + remainderOffset + i)) {
|
|
60
|
-
return
|
|
314
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
61
315
|
}
|
|
62
316
|
}
|
|
63
317
|
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function mapEquals<T, U>(actual: T, expected: U): bool {
|
|
68
|
-
if (actual instanceof Map && expected instanceof Map) {
|
|
69
|
-
if (actual.size != expected.size) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const expectedKeys = expected.keys();
|
|
74
|
-
|
|
75
|
-
for (let i = 0; i < expectedKeys.length; i++) {
|
|
76
|
-
const key = expectedKeys[i];
|
|
77
|
-
|
|
78
|
-
if (!actual.has(key)) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!equals(actual.get(key), expected.get(key))) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return false;
|
|
318
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
91
319
|
}
|
|
92
320
|
|
|
93
321
|
/**
|
|
@@ -98,9 +326,11 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
98
326
|
// both refs
|
|
99
327
|
if (isReference<T>() && isReference<U>()) {
|
|
100
328
|
const actualIsNullable = isNullable<T>();
|
|
101
|
-
|
|
329
|
+
// Use changetype pointer check instead of `== null` to avoid invoking
|
|
330
|
+
// user-defined @operator("==") overloads, which reject null arguments
|
|
331
|
+
const actualIsNull = actualIsNullable && changetype<usize>(actual) == 0;
|
|
102
332
|
const expectedIsNullable = isNullable<U>();
|
|
103
|
-
const expectedIsNull = expectedIsNullable && expected ==
|
|
333
|
+
const expectedIsNull = expectedIsNullable && changetype<usize>(expected) == 0;
|
|
104
334
|
|
|
105
335
|
// null refs
|
|
106
336
|
if (actualIsNull && expectedIsNull) {
|
|
@@ -116,12 +346,26 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
116
346
|
// object refs
|
|
117
347
|
return changetype<usize>(actual) == changetype<usize>(expected);
|
|
118
348
|
}
|
|
119
|
-
} else if (isReference<T>() && !isReference<U>()) {
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
349
|
+
} else if (isReference<T>() && !isReference<U>()) {
|
|
350
|
+
// Both null/zero: null reference matches bare null (usize(0))
|
|
351
|
+
if (changetype<usize>(actual) == 0 && expected == usize(0)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
// Non-null reference vs value type: fundamentally incomparable
|
|
355
|
+
throw new Error(
|
|
356
|
+
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
357
|
+
+ equalsPathAtSuffix() + ": reference and value types are not comparable."
|
|
358
|
+
);
|
|
359
|
+
} else if (!isReference<T>() && isReference<U>()) {
|
|
360
|
+
// Both null/zero: bare null (usize(0)) matches null reference
|
|
361
|
+
if (actual == usize(0) && changetype<usize>(expected) == 0) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
// Value type vs non-null reference: fundamentally incomparable
|
|
365
|
+
throw new Error(
|
|
366
|
+
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
367
|
+
+ equalsPathAtSuffix() + ": reference and value types are not comparable."
|
|
368
|
+
);
|
|
125
369
|
} else { // both primitives
|
|
126
370
|
if ( (isBoolean<T>() && !isBoolean<U>()) || (!isBoolean<T>() && isBoolean<U>())
|
|
127
371
|
) {
|
|
@@ -153,6 +397,7 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
153
397
|
if (sizeof<U>() >= sizeof<T>()) {
|
|
154
398
|
throw new Error(
|
|
155
399
|
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
400
|
+
+ equalsPathAtSuffix()
|
|
156
401
|
+ ": float precision is insufficient for the integer type's range."
|
|
157
402
|
+ " Cast both values to f64 before comparing, e.g. expect(f64(a)).toBe(f64(b))."
|
|
158
403
|
+ " Note: large integer values may lose precision when cast to f64, which could cause false positives."
|
|
@@ -162,18 +407,24 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
162
407
|
if (sizeof<T>() >= sizeof<U>()) {
|
|
163
408
|
throw new Error(
|
|
164
409
|
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
410
|
+
+ equalsPathAtSuffix()
|
|
165
411
|
+ ": float precision is insufficient for the integer type's range."
|
|
166
412
|
+ " Cast both values to f64 before comparing, e.g. expect(f64(a)).toBe(f64(b))."
|
|
167
413
|
+ " Note: large integer values may lose precision when cast to f64, which could cause false positives."
|
|
168
414
|
);
|
|
169
415
|
}
|
|
170
416
|
}
|
|
417
|
+
|
|
418
|
+
// if we got here, cast to f64 is safe without precision loss - cast to compare
|
|
171
419
|
return f64(actual) === f64(expected);
|
|
172
420
|
} else if (isVector<T>() && isVector<U>()) {
|
|
173
421
|
return <v128>actual == <v128>expected;
|
|
174
422
|
} else {
|
|
175
|
-
|
|
176
|
-
|
|
423
|
+
throw new Error(
|
|
424
|
+
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
425
|
+
+ equalsPathAtSuffix() + ": incompatible types."
|
|
426
|
+
);
|
|
427
|
+
}
|
|
177
428
|
}
|
|
178
429
|
}
|
|
179
430
|
|
|
@@ -213,9 +464,13 @@ export function closeTo<T, U>(actual: T, expected: U, precision: i32 = 2): bool
|
|
|
213
464
|
|
|
214
465
|
/**
|
|
215
466
|
* Generic value equality comparison. Assumes comparable types for both values.
|
|
216
|
-
*
|
|
467
|
+
* Supports primitives, strings, Arrays, Sets, Maps, ArrayBuffers, and user-defined
|
|
468
|
+
* types (via compiler transform-injected deep equality method).
|
|
469
|
+
*
|
|
470
|
+
* Returns an __vitest_assemblyscript_EqualityResult enum to distinguish between "not equal" and "type mismatch",
|
|
471
|
+
* enabling matchers to produce more informative assertion failure messages.
|
|
217
472
|
*/
|
|
218
|
-
export function equals<T, U>(actual: T, expected: U):
|
|
473
|
+
export function equals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
219
474
|
let exactMatch: bool = false;
|
|
220
475
|
|
|
221
476
|
// allow boolean-to-number comparisons here
|
|
@@ -229,22 +484,37 @@ export function equals<T, U>(actual: T, expected: U): bool {
|
|
|
229
484
|
|
|
230
485
|
if (!isReference<T>() || isString<T>() || isVector<T>()) {
|
|
231
486
|
// non-bool primitives or strings: return result of comparing
|
|
232
|
-
return exactMatch;
|
|
487
|
+
return exactMatch ? __vitest_assemblyscript_EqualityResult.Equal : __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
233
488
|
} else if (exactMatch) {
|
|
234
489
|
// primitive / reference comparison passed already
|
|
235
|
-
return
|
|
490
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
236
491
|
}
|
|
237
492
|
|
|
238
493
|
if (isNullable<T>()) {
|
|
239
|
-
|
|
240
|
-
|
|
494
|
+
// Use changetype pointer checks instead of `== null` / `!= null` to avoid
|
|
495
|
+
// invoking user-defined @operator("==") overloads, which reject null arguments
|
|
496
|
+
const actualIsNull = changetype<usize>(actual) == 0;
|
|
497
|
+
const expectedIsNull = changetype<usize>(expected) == 0;
|
|
498
|
+
|
|
499
|
+
if (actualIsNull && expectedIsNull) {
|
|
500
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
241
501
|
}
|
|
242
502
|
|
|
243
|
-
if (
|
|
244
|
-
return
|
|
503
|
+
if (actualIsNull != expectedIsNull) {
|
|
504
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
245
505
|
}
|
|
246
506
|
}
|
|
247
507
|
|
|
508
|
+
// Cycle detection: if this reference pair is already being compared further up
|
|
509
|
+
// the call stack, the cycle structure matches — any field-level differences would
|
|
510
|
+
// have been caught in the non-cyclic part before reaching the cycle.
|
|
511
|
+
const actualPtr = changetype<usize>(actual);
|
|
512
|
+
const expectedPtr = changetype<usize>(expected);
|
|
513
|
+
if (equalsRefPairSeen(actualPtr, expectedPtr)) {
|
|
514
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
515
|
+
}
|
|
516
|
+
equalsRefPairMark(actualPtr, expectedPtr);
|
|
517
|
+
|
|
248
518
|
if (isArrayLike<T>(actual) && isArrayLike<U>(expected)) {
|
|
249
519
|
return arrayEquals(actual, expected);
|
|
250
520
|
}
|
|
@@ -258,17 +528,131 @@ export function equals<T, U>(actual: T, expected: U): bool {
|
|
|
258
528
|
return arrayBufferEquals(actual, expected);
|
|
259
529
|
}
|
|
260
530
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
531
|
+
// Runtime type checking for reference types.
|
|
532
|
+
// Managed objects have a header with rtId (runtime type ID) that reflects the actual
|
|
533
|
+
// runtime type, even when the variable is declared as a base type. Unmanaged objects
|
|
534
|
+
// lack this header and fall back to compile-time idof checks.
|
|
535
|
+
if (isManaged<T>() != isManaged<U>()) {
|
|
536
|
+
// Managed vs unmanaged is a fundamental memory layout incompatibility
|
|
537
|
+
throw new Error("Cannot compare deep equality between managed and unmanaged types: "
|
|
538
|
+
+ nameof<T>() + " and " + nameof<U>()
|
|
539
|
+
+ equalsPathAtSuffix()
|
|
264
540
|
);
|
|
265
541
|
}
|
|
266
542
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
543
|
+
if (isManaged<T>() && isManaged<U>()) {
|
|
544
|
+
// Both managed: read runtime type ID from the AS object header
|
|
545
|
+
const actualRtId = load<u32>(changetype<usize>(actual) - MANAGED_OBJECT_RTID_BYTE_OFFSET);
|
|
546
|
+
const expectedRtId = load<u32>(changetype<usize>(expected) - MANAGED_OBJECT_RTID_BYTE_OFFSET);
|
|
547
|
+
|
|
548
|
+
if (actualRtId != expectedRtId) {
|
|
549
|
+
// @ts-ignore
|
|
550
|
+
if (isDefined(actual.__vitest_assemblyscript_deep_equals)) {
|
|
551
|
+
// User-defined classes: return TypeMismatch so the matcher can produce an
|
|
552
|
+
// informative assertion failure message instead of an opaque error.
|
|
553
|
+
//
|
|
554
|
+
// We return here instead of throwing to support `.not.toEqual()` for
|
|
555
|
+
// polymorphic runtime type mismatches — e.g. a Shape-typed Circle vs
|
|
556
|
+
// Shape-typed Square, where asserting "not equal" is valid, not a
|
|
557
|
+
// programmer error. In `toEqual`, `equals()` is called BEFORE
|
|
558
|
+
// `assertComparison()`. If `equals()` throws, execution never reaches
|
|
559
|
+
// `assertComparison`, so `.not` inversion cannot run and the test
|
|
560
|
+
// crashes. By returning TypeMismatch, `toEqual` evaluates
|
|
561
|
+
// `result == __vitest_assemblyscript_EqualityResult.Equal` (false), passes that to
|
|
562
|
+
// `assertComparison`, and `.not` can invert it to a pass.
|
|
563
|
+
|
|
564
|
+
// Capture runtime type names via virtual dispatch for informative assertion suffix
|
|
565
|
+
// @ts-ignore
|
|
566
|
+
if (isDefined(actual.__vitest_assemblyscript_typename)) {
|
|
567
|
+
// @ts-ignore
|
|
568
|
+
equalsRtmActualName = (<NonNullable<T>>actual).__vitest_assemblyscript_typename();
|
|
569
|
+
}
|
|
570
|
+
// @ts-ignore
|
|
571
|
+
if (isDefined(expected.__vitest_assemblyscript_typename)) {
|
|
572
|
+
// @ts-ignore
|
|
573
|
+
equalsRtmExpectedName = (<NonNullable<U>>expected).__vitest_assemblyscript_typename();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return __vitest_assemblyscript_EqualityResult.RuntimeTypeMismatch;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Non-user-defined managed types with mismatched rtIds: cross-container comparisons
|
|
580
|
+
// (e.g. Map vs Set, Array vs Map) that fell through the instanceof checks above
|
|
581
|
+
// (which require both operands to be the same container type), or stdlib types
|
|
582
|
+
throw new Error("Cannot compare deep equality between " + nameof<T>()
|
|
583
|
+
+ " and " + nameof<U>()
|
|
584
|
+
+ equalsPathAtSuffix()
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
// Both unmanaged: no object header or idof available, fall back to compile-time
|
|
589
|
+
// nameof check. This is acceptable because unmanaged types don't participate in
|
|
590
|
+
// virtual dispatch or polymorphic inheritance — the compile-time type is reliable.
|
|
591
|
+
if (nameof<T>() != nameof<U>()) {
|
|
592
|
+
// @ts-ignore
|
|
593
|
+
if (isDefined(actual.__vitest_assemblyscript_deep_equals)) {
|
|
594
|
+
// see both-managed case above: same reasoning here, just behind a different type check
|
|
595
|
+
// Unmanaged types don't have virtual dispatch, so typename (if present) returns
|
|
596
|
+
// compile-time names — consistent with how the type check itself works here.
|
|
597
|
+
// @ts-ignore
|
|
598
|
+
if (isDefined(actual.__vitest_assemblyscript_typename)) {
|
|
599
|
+
// @ts-ignore
|
|
600
|
+
equalsRtmActualName = (<NonNullable<T>>actual).__vitest_assemblyscript_typename();
|
|
601
|
+
}
|
|
602
|
+
// @ts-ignore
|
|
603
|
+
if (isDefined(expected.__vitest_assemblyscript_typename)) {
|
|
604
|
+
// @ts-ignore
|
|
605
|
+
equalsRtmExpectedName = (<NonNullable<U>>expected).__vitest_assemblyscript_typename();
|
|
606
|
+
}
|
|
607
|
+
return __vitest_assemblyscript_EqualityResult.RuntimeTypeMismatch;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// see both-managed case above: handle potential mismatched type fallthrough
|
|
611
|
+
// for unmanaged stdlib / container type mismatches
|
|
612
|
+
throw new Error("Cannot compare deep equality between " + nameof<T>()
|
|
613
|
+
+ " and " + nameof<U>()
|
|
614
|
+
+ equalsPathAtSuffix()
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// @ts-ignore
|
|
620
|
+
// User-defined reference types: delegate to compiler transform-injected deep equality
|
|
621
|
+
// method. Uses hard-coded method name because using a variable like `actual[DEEP_EQ_FUNC]`
|
|
622
|
+
// requires the class to define an index signature.
|
|
623
|
+
// Cast to NonNullable<T> because AS doesn't narrow nullability from the changetype-based
|
|
624
|
+
// null checks above — it requires explicit type narrowing to call methods on nullable types.
|
|
625
|
+
// Safe because both-null and one-null cases return early above.
|
|
626
|
+
if (isDefined(actual.__vitest_assemblyscript_deep_equals)) {
|
|
627
|
+
const nonNullActual = <NonNullable<T>>actual;
|
|
628
|
+
// @ts-ignore
|
|
629
|
+
return nonNullActual.__vitest_assemblyscript_deep_equals(changetype<usize>(expected));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Fall back to reference identity for types without deep equality method
|
|
633
|
+
return changetype<usize>(actual) == changetype<usize>(expected)
|
|
634
|
+
? __vitest_assemblyscript_EqualityResult.Equal
|
|
635
|
+
: __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Global bridge for the deep-equals compiler transform.
|
|
640
|
+
*
|
|
641
|
+
* Injected deep equality methods in user classes call this function for per-field
|
|
642
|
+
* comparisons. Declared global to make it available in all source files without import,
|
|
643
|
+
* solving the afterParse import resolution limitation (injected import statements
|
|
644
|
+
* are not processed by the AS compiler).
|
|
645
|
+
*
|
|
646
|
+
* Returns __vitest_assemblyscript_EqualityResult so injected methods can propagate type mismatch information
|
|
647
|
+
* from nested comparisons back to the top-level matcher.
|
|
648
|
+
*
|
|
649
|
+
* Loaded into the compilation transitively: user test imports
|
|
650
|
+
* vitest-pool-assemblyscript/assembly → index.ts → compare.ts.
|
|
651
|
+
*/
|
|
652
|
+
// @ts-ignore
|
|
653
|
+
@global
|
|
654
|
+
function __vitest_assemblyscript_compare_equals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
655
|
+
return equals<T, U>(actual, expected);
|
|
272
656
|
}
|
|
273
657
|
|
|
274
658
|
export enum InequalityOperation {
|
|
@@ -402,29 +786,3 @@ export function truthyOrFalsey<T>(actual: T, expected: bool): bool {
|
|
|
402
786
|
return actual ? expected == true : expected == false;
|
|
403
787
|
}
|
|
404
788
|
|
|
405
|
-
export function isNull<T>(value: T): bool {
|
|
406
|
-
if (isReference<T>()) {
|
|
407
|
-
if (isNullable<T>()) {
|
|
408
|
-
return value == null;
|
|
409
|
-
} else {
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
if (isBoolean<T>()) {
|
|
414
|
-
return false;
|
|
415
|
-
} else if (isVector<T>()) {
|
|
416
|
-
return false;
|
|
417
|
-
} else {
|
|
418
|
-
return nameof<T>(value) == 'usize' && value == 0;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
export function nan<T>(value: T): bool {
|
|
424
|
-
if (isFloat<T>()) {
|
|
425
|
-
// @ts-ignore
|
|
426
|
-
return isNaN<T>(value);
|
|
427
|
-
} else {
|
|
428
|
-
return false;
|
|
429
|
-
}
|
|
430
|
-
}
|