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/compare.ts
CHANGED
|
@@ -1,61 +1,321 @@
|
|
|
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;
|
|
33
229
|
}
|
|
34
230
|
|
|
35
|
-
function mapEquals<T, U>(actual: T, expected: U):
|
|
231
|
+
function mapEquals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
36
232
|
if (actual instanceof Map && expected instanceof Map) {
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
);
|
|
39
241
|
}
|
|
40
242
|
|
|
41
|
-
|
|
243
|
+
if (actual.size != expected.size) {
|
|
244
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
245
|
+
}
|
|
42
246
|
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
|
45
269
|
|
|
46
|
-
|
|
47
|
-
|
|
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();
|
|
48
277
|
}
|
|
49
278
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function arrayBufferEquals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
290
|
+
if (!(actual instanceof ArrayBuffer) || !(expected instanceof ArrayBuffer)) {
|
|
291
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (actual.byteLength != expected.byteLength) {
|
|
295
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const actualPtr = changetype<usize>(actual);
|
|
299
|
+
const expectedPtr = changetype<usize>(expected);
|
|
300
|
+
const wordCount: usize = actual.byteLength / 8;
|
|
301
|
+
const remainder: usize = actual.byteLength % 8;
|
|
302
|
+
|
|
303
|
+
// compare 8 bytes at a time (u64 word-sized comparison)
|
|
304
|
+
for (let i: usize = 0; i < wordCount; i++) {
|
|
305
|
+
if (load<u64>(actualPtr + i * 8) != load<u64>(expectedPtr + i * 8)) {
|
|
306
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
53
307
|
}
|
|
308
|
+
}
|
|
54
309
|
|
|
55
|
-
|
|
310
|
+
// compare remaining 0-7 bytes individually
|
|
311
|
+
const remainderOffset = wordCount * 8;
|
|
312
|
+
for (let i: usize = 0; i < remainder; i++) {
|
|
313
|
+
if (load<u8>(actualPtr + remainderOffset + i) != load<u8>(expectedPtr + remainderOffset + i)) {
|
|
314
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
315
|
+
}
|
|
56
316
|
}
|
|
57
317
|
|
|
58
|
-
return
|
|
318
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
59
319
|
}
|
|
60
320
|
|
|
61
321
|
/**
|
|
@@ -66,9 +326,11 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
66
326
|
// both refs
|
|
67
327
|
if (isReference<T>() && isReference<U>()) {
|
|
68
328
|
const actualIsNullable = isNullable<T>();
|
|
69
|
-
|
|
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;
|
|
70
332
|
const expectedIsNullable = isNullable<U>();
|
|
71
|
-
const expectedIsNull = expectedIsNullable && expected ==
|
|
333
|
+
const expectedIsNull = expectedIsNullable && changetype<usize>(expected) == 0;
|
|
72
334
|
|
|
73
335
|
// null refs
|
|
74
336
|
if (actualIsNull && expectedIsNull) {
|
|
@@ -84,12 +346,26 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
84
346
|
// object refs
|
|
85
347
|
return changetype<usize>(actual) == changetype<usize>(expected);
|
|
86
348
|
}
|
|
87
|
-
} else if (isReference<T>() && !isReference<U>()) {
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
);
|
|
93
369
|
} else { // both primitives
|
|
94
370
|
if ( (isBoolean<T>() && !isBoolean<U>()) || (!isBoolean<T>() && isBoolean<U>())
|
|
95
371
|
) {
|
|
@@ -121,6 +397,7 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
121
397
|
if (sizeof<U>() >= sizeof<T>()) {
|
|
122
398
|
throw new Error(
|
|
123
399
|
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
400
|
+
+ equalsPathAtSuffix()
|
|
124
401
|
+ ": float precision is insufficient for the integer type's range."
|
|
125
402
|
+ " Cast both values to f64 before comparing, e.g. expect(f64(a)).toBe(f64(b))."
|
|
126
403
|
+ " Note: large integer values may lose precision when cast to f64, which could cause false positives."
|
|
@@ -130,18 +407,24 @@ export function identical<T, U>(actual: T, expected: U): bool {
|
|
|
130
407
|
if (sizeof<T>() >= sizeof<U>()) {
|
|
131
408
|
throw new Error(
|
|
132
409
|
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
410
|
+
+ equalsPathAtSuffix()
|
|
133
411
|
+ ": float precision is insufficient for the integer type's range."
|
|
134
412
|
+ " Cast both values to f64 before comparing, e.g. expect(f64(a)).toBe(f64(b))."
|
|
135
413
|
+ " Note: large integer values may lose precision when cast to f64, which could cause false positives."
|
|
136
414
|
);
|
|
137
415
|
}
|
|
138
416
|
}
|
|
417
|
+
|
|
418
|
+
// if we got here, cast to f64 is safe without precision loss - cast to compare
|
|
139
419
|
return f64(actual) === f64(expected);
|
|
140
420
|
} else if (isVector<T>() && isVector<U>()) {
|
|
141
421
|
return <v128>actual == <v128>expected;
|
|
142
422
|
} else {
|
|
143
|
-
|
|
144
|
-
|
|
423
|
+
throw new Error(
|
|
424
|
+
"Cannot compare " + nameof<T>() + " with " + nameof<U>()
|
|
425
|
+
+ equalsPathAtSuffix() + ": incompatible types."
|
|
426
|
+
);
|
|
427
|
+
}
|
|
145
428
|
}
|
|
146
429
|
}
|
|
147
430
|
|
|
@@ -181,9 +464,13 @@ export function closeTo<T, U>(actual: T, expected: U, precision: i32 = 2): bool
|
|
|
181
464
|
|
|
182
465
|
/**
|
|
183
466
|
* Generic value equality comparison. Assumes comparable types for both values.
|
|
184
|
-
*
|
|
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.
|
|
185
472
|
*/
|
|
186
|
-
export function equals<T, U>(actual: T, expected: U):
|
|
473
|
+
export function equals<T, U>(actual: T, expected: U): __vitest_assemblyscript_EqualityResult {
|
|
187
474
|
let exactMatch: bool = false;
|
|
188
475
|
|
|
189
476
|
// allow boolean-to-number comparisons here
|
|
@@ -197,22 +484,37 @@ export function equals<T, U>(actual: T, expected: U): bool {
|
|
|
197
484
|
|
|
198
485
|
if (!isReference<T>() || isString<T>() || isVector<T>()) {
|
|
199
486
|
// non-bool primitives or strings: return result of comparing
|
|
200
|
-
return exactMatch;
|
|
487
|
+
return exactMatch ? __vitest_assemblyscript_EqualityResult.Equal : __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
201
488
|
} else if (exactMatch) {
|
|
202
489
|
// primitive / reference comparison passed already
|
|
203
|
-
return
|
|
490
|
+
return __vitest_assemblyscript_EqualityResult.Equal;
|
|
204
491
|
}
|
|
205
492
|
|
|
206
493
|
if (isNullable<T>()) {
|
|
207
|
-
|
|
208
|
-
|
|
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;
|
|
209
501
|
}
|
|
210
502
|
|
|
211
|
-
if (
|
|
212
|
-
return
|
|
503
|
+
if (actualIsNull != expectedIsNull) {
|
|
504
|
+
return __vitest_assemblyscript_EqualityResult.NotEqual;
|
|
213
505
|
}
|
|
214
506
|
}
|
|
215
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
|
+
|
|
216
518
|
if (isArrayLike<T>(actual) && isArrayLike<U>(expected)) {
|
|
217
519
|
return arrayEquals(actual, expected);
|
|
218
520
|
}
|
|
@@ -222,18 +524,135 @@ export function equals<T, U>(actual: T, expected: U): bool {
|
|
|
222
524
|
if (actual instanceof Map && expected instanceof Map) {
|
|
223
525
|
return mapEquals(actual, expected);
|
|
224
526
|
}
|
|
527
|
+
if (actual instanceof ArrayBuffer && expected instanceof ArrayBuffer) {
|
|
528
|
+
return arrayBufferEquals(actual, expected);
|
|
529
|
+
}
|
|
225
530
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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()
|
|
229
540
|
);
|
|
230
541
|
}
|
|
231
542
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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);
|
|
237
656
|
}
|
|
238
657
|
|
|
239
658
|
export enum InequalityOperation {
|
|
@@ -367,29 +786,3 @@ export function truthyOrFalsey<T>(actual: T, expected: bool): bool {
|
|
|
367
786
|
return actual ? expected == true : expected == false;
|
|
368
787
|
}
|
|
369
788
|
|
|
370
|
-
export function isNull<T>(value: T): bool {
|
|
371
|
-
if (isReference<T>()) {
|
|
372
|
-
if (isNullable<T>()) {
|
|
373
|
-
return value == null;
|
|
374
|
-
} else {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
} else {
|
|
378
|
-
if (isBoolean<T>()) {
|
|
379
|
-
return false;
|
|
380
|
-
} else if (isVector<T>()) {
|
|
381
|
-
return false;
|
|
382
|
-
} else {
|
|
383
|
-
return nameof<T>(value) == 'usize' && value == 0;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
export function nan<T>(value: T): bool {
|
|
389
|
-
if (isFloat<T>()) {
|
|
390
|
-
// @ts-ignore
|
|
391
|
-
return isNaN<T>(value);
|
|
392
|
-
} else {
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
}
|