as-test 1.3.0 → 1.4.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/CHANGELOG.md +41 -0
- package/README.md +1 -4
- package/assembly/index.ts +66 -47
- package/assembly/src/expectation.ts +44 -86
- package/assembly/src/fuzz.ts +10 -10
- package/assembly/src/log.ts +3 -3
- package/assembly/src/reflect.ts +122 -0
- package/assembly/src/stringify.ts +240 -0
- package/assembly/src/suite.ts +48 -27
- package/assembly/src/tests.ts +7 -7
- package/assembly/util/wipc.ts +2 -2
- package/bin/build-worker-pool.js +9 -0
- package/bin/build-worker.js +27 -3
- package/bin/commands/build-core.js +144 -82
- package/bin/commands/init-core.js +0 -3
- package/bin/commands/run-core.js +165 -41
- package/bin/commands/run.js +2 -1
- package/bin/commands/test.js +2 -1
- package/bin/dependency-graph.js +0 -0
- package/bin/index.js +534 -79
- package/bin/reporters/default.js +34 -0
- package/bin/util.js +9 -0
- package/package.json +3 -7
- package/transform/lib/equals.js +388 -0
- package/transform/lib/index.js +2 -0
- package/transform/lib/log.js +3 -7
- package/transform/lib/types.js +4 -2
- package/transform/lib/transform.js +0 -502
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
|
|
2
|
+
|
|
3
|
+
// Structural equality entry point. Modelled on as-pect's Reflect.equals,
|
|
4
|
+
// trimmed of memoisation cache and ignore-list bookkeeping — those live
|
|
5
|
+
// in the per-class methods emitted by the EqualsTransform.
|
|
6
|
+
//
|
|
7
|
+
// Per-call flow:
|
|
8
|
+
// * primitives, strings, booleans → `===`
|
|
9
|
+
// * nullables → resolve null on either side, then fall through
|
|
10
|
+
// * arrays → length + element-wise recursion with the same `stack`
|
|
11
|
+
// * managed values → identity short-circuit, optional `rtId` check
|
|
12
|
+
// for strict mode, cycle detection via `stack`, then dispatch to
|
|
13
|
+
// the class's `__AS_TEST_EQUALS(other, stack, ignore, strict)`
|
|
14
|
+
//
|
|
15
|
+
// `right` is forwarded with its full static type so the dispatched
|
|
16
|
+
// method's body reads fields directly via AS virtual dispatch.
|
|
17
|
+
|
|
18
|
+
export function reflectEquals<T>(
|
|
19
|
+
left: T,
|
|
20
|
+
right: T,
|
|
21
|
+
stack: usize[],
|
|
22
|
+
strict: bool,
|
|
23
|
+
): bool {
|
|
24
|
+
if (isBoolean<T>() || isInteger<T>() || isFloat<T>() || isString<T>())
|
|
25
|
+
return left === right;
|
|
26
|
+
|
|
27
|
+
if (isNullable<T>()) {
|
|
28
|
+
const lp = changetype<usize>(left);
|
|
29
|
+
const rp = changetype<usize>(right);
|
|
30
|
+
if (lp == 0 || rp == 0) return lp == rp;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (isArray<T>()) {
|
|
34
|
+
// @ts-expect-error: type
|
|
35
|
+
const aLen = (left as valueof<T>[]).length;
|
|
36
|
+
// @ts-expect-error: type
|
|
37
|
+
const bLen = (right as valueof<T>[]).length;
|
|
38
|
+
if (aLen != bLen) return false;
|
|
39
|
+
for (let i = 0; i < aLen; i++) {
|
|
40
|
+
if (
|
|
41
|
+
// @ts-expect-error: type
|
|
42
|
+
!reflectEquals<valueof<T>>(
|
|
43
|
+
// @ts-expect-error: type
|
|
44
|
+
unchecked((left as valueof<T>[])[i]),
|
|
45
|
+
// @ts-expect-error: type
|
|
46
|
+
unchecked((right as valueof<T>[])[i]),
|
|
47
|
+
stack,
|
|
48
|
+
strict,
|
|
49
|
+
)
|
|
50
|
+
) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isManaged<T>()) {
|
|
58
|
+
const lp = changetype<usize>(left);
|
|
59
|
+
const rp = changetype<usize>(right);
|
|
60
|
+
if (lp == rp) return true;
|
|
61
|
+
if (lp == 0 || rp == 0) return false;
|
|
62
|
+
|
|
63
|
+
if (strict) {
|
|
64
|
+
const lo = changetype<OBJECT>(lp - TOTAL_OVERHEAD);
|
|
65
|
+
const ro = changetype<OBJECT>(rp - TOTAL_OVERHEAD);
|
|
66
|
+
if (lo.rtId != ro.rtId) return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < stack.length; i += 2) {
|
|
70
|
+
if (unchecked(stack[i]) == lp && unchecked(stack[i + 1]) == rp)
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
stack.push(lp);
|
|
75
|
+
stack.push(rp);
|
|
76
|
+
|
|
77
|
+
// Pass `right` with its full static type so the dispatched method's
|
|
78
|
+
// body can read fields directly. For nullable T, strip the
|
|
79
|
+
// nullability with `!` — we returned early above if either operand
|
|
80
|
+
// was null, so `right!` is safe at this point.
|
|
81
|
+
//
|
|
82
|
+
// The transform injects `__AS_TEST_EQUALS` on every user class the
|
|
83
|
+
// compiler reaches, but third-party generic types (Map, Set,
|
|
84
|
+
// Option<T>, …) don't carry it. Fall back to the class's `==`
|
|
85
|
+
// operator (which the user can overload via `@operator("==")`),
|
|
86
|
+
// collapsing to reference identity if no overload is present.
|
|
87
|
+
let passed: bool;
|
|
88
|
+
if (isNullable<T>()) {
|
|
89
|
+
// @ts-expect-error: optional method, presence is a compile-time check
|
|
90
|
+
if (isDefined(left!.__AS_TEST_EQUALS)) {
|
|
91
|
+
// @ts-expect-error: declared by transform
|
|
92
|
+
passed = left!.__AS_TEST_EQUALS(
|
|
93
|
+
right!,
|
|
94
|
+
stack,
|
|
95
|
+
[] as StaticArray<i64>,
|
|
96
|
+
strict,
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
passed = left == right;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// @ts-expect-error: optional method, presence is a compile-time check
|
|
103
|
+
if (isDefined(left.__AS_TEST_EQUALS)) {
|
|
104
|
+
// @ts-expect-error: declared by transform
|
|
105
|
+
passed = left.__AS_TEST_EQUALS(
|
|
106
|
+
right,
|
|
107
|
+
stack,
|
|
108
|
+
[] as StaticArray<i64>,
|
|
109
|
+
strict,
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
passed = left == right;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
stack.pop();
|
|
117
|
+
stack.pop();
|
|
118
|
+
return passed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return left === right;
|
|
122
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Tiny in-tree replacement for json-as's runtime. Used for two jobs:
|
|
2
|
+
//
|
|
3
|
+
// 1. Stringifying user values for the wipc assertion-report payload
|
|
4
|
+
// (was `JSON.stringify<T>(value)`).
|
|
5
|
+
// 2. JSON-escaping internal strings before splicing them into hand-built
|
|
6
|
+
// wire payloads (was `JSON.stringify<string>(s)`).
|
|
7
|
+
//
|
|
8
|
+
// Dispatch order in `stringify<T>`:
|
|
9
|
+
// * primitives / booleans / numbers → `.toString()`
|
|
10
|
+
// * strings → JSON-escape + quote
|
|
11
|
+
// * nullable null → "null"
|
|
12
|
+
// * Date → quoted ISO-8601 string
|
|
13
|
+
// * ArrayBuffer → array of unsigned byte values
|
|
14
|
+
// * TypedArray (ArrayBufferView) → array of element values
|
|
15
|
+
// * Array / StaticArray → element-wise recursion
|
|
16
|
+
// * Set → array of values
|
|
17
|
+
// * Map → object with stringified keys
|
|
18
|
+
// * managed with `toJSON(): string` → call it
|
|
19
|
+
// * managed without `toJSON()` → "<TypeName>" placeholder
|
|
20
|
+
//
|
|
21
|
+
// The Date/ArrayBuffer/typed-array/StaticArray/Set/Map branches use
|
|
22
|
+
// `value instanceof X` guards. In a generic function AssemblyScript resolves
|
|
23
|
+
// these statically: the branch is only compiled when `T` can actually be that
|
|
24
|
+
// type and pruned otherwise, so the recursive calls inside type-check against
|
|
25
|
+
// the real K/V/element types.
|
|
26
|
+
//
|
|
27
|
+
// Classes decorated with `@json` / `@serializable` are skipped by the
|
|
28
|
+
// EqualsTransform's toJSON injector. Users who want those classes to
|
|
29
|
+
// render prettily in reports can add their own one-line `toJSON()`
|
|
30
|
+
// (e.g. `return JSON.stringify(this);` if they've also wired up
|
|
31
|
+
// `--transform json-as`). as-test itself stays json-as-free.
|
|
32
|
+
|
|
33
|
+
export function stringify<T>(value: T): string {
|
|
34
|
+
if (isBoolean<T>()) return value ? "true" : "false";
|
|
35
|
+
if (isInteger<T>() || isFloat<T>()) {
|
|
36
|
+
// @ts-ignore: every numeric AS primitive carries toString()
|
|
37
|
+
return value.toString();
|
|
38
|
+
}
|
|
39
|
+
if (isString<T>()) {
|
|
40
|
+
return escape(value as string);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (isNullable<T>() && changetype<usize>(value) == 0) return "null";
|
|
44
|
+
|
|
45
|
+
// Date → quoted ISO-8601 string (matches `JSON.stringify(new Date(...))`).
|
|
46
|
+
if (value instanceof Date) {
|
|
47
|
+
return escape((value as Date).toISOString());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ArrayBuffer → array of its unsigned byte values. A raw buffer has no
|
|
51
|
+
// natural JSON form, so surface the bytes for debugging.
|
|
52
|
+
if (value instanceof ArrayBuffer) {
|
|
53
|
+
const view = Uint8Array.wrap(value as ArrayBuffer);
|
|
54
|
+
const len = view.length;
|
|
55
|
+
if (len == 0) return "[]";
|
|
56
|
+
let out = "[";
|
|
57
|
+
for (let i = 0; i < len; i++) {
|
|
58
|
+
if (i > 0) out += ",";
|
|
59
|
+
out += unchecked(view[i]).toString();
|
|
60
|
+
}
|
|
61
|
+
return out + "]";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Typed arrays (Int32Array, Float64Array, …) all extend ArrayBufferView.
|
|
65
|
+
if (value instanceof ArrayBufferView) {
|
|
66
|
+
// @ts-ignore: every typed array carries a typesafe length + indexer
|
|
67
|
+
const len = value.length;
|
|
68
|
+
if (len == 0) return "[]";
|
|
69
|
+
let out = "[";
|
|
70
|
+
for (let i = 0; i < len; i++) {
|
|
71
|
+
if (i > 0) out += ",";
|
|
72
|
+
// @ts-ignore: element is the view's numeric valueof type
|
|
73
|
+
out += stringify(unchecked(value[i]));
|
|
74
|
+
}
|
|
75
|
+
return out + "]";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isArray<T>()) {
|
|
79
|
+
// @ts-ignore: typesafe length
|
|
80
|
+
const len = (value as valueof<T>[]).length;
|
|
81
|
+
if (len == 0) return "[]";
|
|
82
|
+
let out = "[";
|
|
83
|
+
for (let i = 0; i < len; i++) {
|
|
84
|
+
if (i > 0) out += ",";
|
|
85
|
+
out += stringify<valueof<T>>(
|
|
86
|
+
// @ts-ignore: bounds-checked above
|
|
87
|
+
unchecked((value as valueof<T>[])[i]),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return out + "]";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// StaticArray<V> → element-wise recursion, same shape as a regular array.
|
|
94
|
+
if (value instanceof StaticArray) {
|
|
95
|
+
// @ts-ignore: typesafe length + indexer
|
|
96
|
+
const len = value.length;
|
|
97
|
+
if (len == 0) return "[]";
|
|
98
|
+
let out = "[";
|
|
99
|
+
for (let i = 0; i < len; i++) {
|
|
100
|
+
if (i > 0) out += ",";
|
|
101
|
+
// @ts-ignore: element is the array's valueof type
|
|
102
|
+
out += stringify(unchecked(value[i]));
|
|
103
|
+
}
|
|
104
|
+
return out + "]";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Set<V> → array of its values, in insertion order.
|
|
108
|
+
if (value instanceof Set) {
|
|
109
|
+
const vals = value.values();
|
|
110
|
+
const len = vals.length;
|
|
111
|
+
if (len == 0) return "[]";
|
|
112
|
+
let out = "[";
|
|
113
|
+
for (let i = 0; i < len; i++) {
|
|
114
|
+
if (i > 0) out += ",";
|
|
115
|
+
out += stringify(unchecked(vals[i]));
|
|
116
|
+
}
|
|
117
|
+
return out + "]";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Map<K,V> → JSON object. JSON object keys must be strings, so non-string
|
|
121
|
+
// keys are coerced to their `.toString()` form and quoted (e.g. a numeric
|
|
122
|
+
// key `10` becomes `"10"`, matching `JSON.stringify({ 10: ... })`).
|
|
123
|
+
if (value instanceof Map) {
|
|
124
|
+
const keys = value.keys();
|
|
125
|
+
const vals = value.values();
|
|
126
|
+
const len = keys.length;
|
|
127
|
+
if (len == 0) return "{}";
|
|
128
|
+
let out = "{";
|
|
129
|
+
for (let i = 0; i < len; i++) {
|
|
130
|
+
if (i > 0) out += ",";
|
|
131
|
+
out += jsonKey(unchecked(keys[i])) + ":" + stringify(unchecked(vals[i]));
|
|
132
|
+
}
|
|
133
|
+
return out + "}";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isManaged<T>()) {
|
|
137
|
+
// @ts-ignore: hand-written or transform-generated serializer
|
|
138
|
+
if (isDefined(value.toJSON)) return value.toJSON();
|
|
139
|
+
return escape("<" + nameof<T>() + ">");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Unreachable for well-typed AS code — but emit a valid JSON string so
|
|
143
|
+
// the surrounding payload stays parsable.
|
|
144
|
+
return escape("<" + nameof<T>() + ">");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// JSON string escape per RFC 8259, with explicit handling for UTF-16
|
|
148
|
+
// surrogates (matches json-as's serializeString behaviour).
|
|
149
|
+
//
|
|
150
|
+
// * `"`, `\`, control chars U+0000..U+001F → escape sequences
|
|
151
|
+
// * valid surrogate pair (high → low) → pass both code units through
|
|
152
|
+
// * lone surrogate (high not followed by → emit `\uXXXX` for the
|
|
153
|
+
// low, or low without a preceding high) single code unit
|
|
154
|
+
// * everything else → pass through
|
|
155
|
+
export function escape(s: string): string {
|
|
156
|
+
let out = '"';
|
|
157
|
+
const len = s.length;
|
|
158
|
+
for (let i = 0; i < len; i++) {
|
|
159
|
+
const c = s.charCodeAt(i);
|
|
160
|
+
if (c == 0x22 /* " */) {
|
|
161
|
+
out += '\\"';
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (c == 0x5c /* \ */) {
|
|
165
|
+
out += "\\\\";
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (c == 0x08) {
|
|
169
|
+
out += "\\b";
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (c == 0x09) {
|
|
173
|
+
out += "\\t";
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (c == 0x0a) {
|
|
177
|
+
out += "\\n";
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (c == 0x0c) {
|
|
181
|
+
out += "\\f";
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (c == 0x0d) {
|
|
185
|
+
out += "\\r";
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (c < 0x20) {
|
|
189
|
+
out += "\\u00" + hexNibble((c >>> 4) & 0xf) + hexNibble(c & 0xf);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (c >= 0xd800 && c <= 0xdfff) {
|
|
193
|
+
// High surrogate followed by a low surrogate is a valid pair —
|
|
194
|
+
// emit both code units verbatim and skip the low surrogate index.
|
|
195
|
+
if (c <= 0xdbff && i + 1 < len) {
|
|
196
|
+
const next = s.charCodeAt(i + 1);
|
|
197
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
198
|
+
out += String.fromCharCode(c);
|
|
199
|
+
out += String.fromCharCode(next);
|
|
200
|
+
i++;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Lone surrogate — escape the single code unit.
|
|
205
|
+
out += "\\u" + hex4(<u32>c);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
out += String.fromCharCode(c);
|
|
209
|
+
}
|
|
210
|
+
return out + '"';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Render a Map key as a quoted JSON string. Primitive keys are coerced to
|
|
214
|
+
// their textual form first; anything else falls back to the value serializer,
|
|
215
|
+
// wrapped as a string so the surrounding object stays parsable.
|
|
216
|
+
function jsonKey<K>(key: K): string {
|
|
217
|
+
if (isString<K>()) return escape(key as string);
|
|
218
|
+
if (isBoolean<K>()) return escape(key ? "true" : "false");
|
|
219
|
+
if (isInteger<K>() || isFloat<K>()) {
|
|
220
|
+
// @ts-ignore: numeric AS primitives carry toString()
|
|
221
|
+
return escape(key.toString());
|
|
222
|
+
}
|
|
223
|
+
return escape(stringify<K>(key));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@inline function hexNibble(n: u32): string {
|
|
228
|
+
// 0..9 → '0'..'9'; 10..15 → 'a'..'f'.
|
|
229
|
+
return String.fromCharCode(n < 10 ? 0x30 + n : 0x61 + (n - 10));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@inline function hex4(c: u32): string {
|
|
234
|
+
return (
|
|
235
|
+
hexNibble((c >>> 12) & 0xf) +
|
|
236
|
+
hexNibble((c >>> 8) & 0xf) +
|
|
237
|
+
hexNibble((c >>> 4) & 0xf) +
|
|
238
|
+
hexNibble(c & 0xf)
|
|
239
|
+
);
|
|
240
|
+
}
|
package/assembly/src/suite.ts
CHANGED
|
@@ -2,9 +2,14 @@ import { Time } from "..";
|
|
|
2
2
|
import { Expectation } from "./expectation";
|
|
3
3
|
import { Tests } from "./tests";
|
|
4
4
|
import { Log } from "./log";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
after_each_callback,
|
|
7
|
+
after_each_kinds,
|
|
8
|
+
before_each_callback,
|
|
9
|
+
before_each_kinds,
|
|
10
|
+
} from "..";
|
|
6
11
|
import { sendSuiteEnd, sendSuiteStart } from "../util/wipc";
|
|
7
|
-
import {
|
|
12
|
+
import { escape, stringify } from "./stringify";
|
|
8
13
|
|
|
9
14
|
export class Suite {
|
|
10
15
|
public file: string = "unknown";
|
|
@@ -49,10 +54,8 @@ export class Suite {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
run(): void {
|
|
52
|
-
// @ts-ignore
|
|
57
|
+
// @ts-ignore: current_suite is a @global, the parent for nested registration
|
|
53
58
|
current_suite = this;
|
|
54
|
-
// @ts-ignore
|
|
55
|
-
depth++;
|
|
56
59
|
this.time.start = performance.now();
|
|
57
60
|
sendSuiteStart(this.file, this.depth, this.kind, this.description);
|
|
58
61
|
const isSkippedCase =
|
|
@@ -73,8 +76,6 @@ export class Suite {
|
|
|
73
76
|
if (isSkippedCase) {
|
|
74
77
|
this.time.end = performance.now();
|
|
75
78
|
this.verdict = "skip";
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
depth--;
|
|
78
79
|
sendSuiteEnd(
|
|
79
80
|
this.file,
|
|
80
81
|
this.depth,
|
|
@@ -85,14 +86,22 @@ export class Suite {
|
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
// @ts-ignore
|
|
89
|
-
if (
|
|
89
|
+
// @ts-ignore: nullable function import resolved at runtime
|
|
90
|
+
if (
|
|
91
|
+
before_each_callback &&
|
|
92
|
+
hookFiresFor(this.kind, before_each_kinds, isTestCase)
|
|
93
|
+
) {
|
|
94
|
+
before_each_callback();
|
|
95
|
+
}
|
|
90
96
|
this.callback();
|
|
91
|
-
// @ts-ignore
|
|
92
|
-
if (
|
|
97
|
+
// @ts-ignore: nullable function import resolved at runtime
|
|
98
|
+
if (
|
|
99
|
+
after_each_callback &&
|
|
100
|
+
hookFiresFor(this.kind, after_each_kinds, isTestCase)
|
|
101
|
+
) {
|
|
102
|
+
after_each_callback();
|
|
103
|
+
}
|
|
93
104
|
this.time.end = performance.now();
|
|
94
|
-
// @ts-ignore
|
|
95
|
-
depth--;
|
|
96
105
|
|
|
97
106
|
const hasOnlyChildren = this.hasOnlyChildren();
|
|
98
107
|
|
|
@@ -146,16 +155,12 @@ export class Suite {
|
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
skip(): void {
|
|
149
|
-
// @ts-ignore
|
|
158
|
+
// @ts-ignore: current_suite is a @global
|
|
150
159
|
current_suite = this;
|
|
151
|
-
// @ts-ignore
|
|
152
|
-
depth++;
|
|
153
160
|
this.time.start = performance.now();
|
|
154
161
|
this.time.end = this.time.start;
|
|
155
162
|
this.verdict = "skip";
|
|
156
163
|
sendSuiteStart(this.file, this.depth, this.kind, this.description);
|
|
157
|
-
// @ts-ignore
|
|
158
|
-
depth--;
|
|
159
164
|
sendSuiteEnd(
|
|
160
165
|
this.file,
|
|
161
166
|
this.depth,
|
|
@@ -172,31 +177,47 @@ export class Suite {
|
|
|
172
177
|
return false;
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
toJSON(): string {
|
|
176
181
|
let out = "{";
|
|
177
182
|
if (this.depth <= 0) {
|
|
178
|
-
out += '"file":' +
|
|
183
|
+
out += '"file":' + escape(this.file) + ",";
|
|
179
184
|
}
|
|
180
185
|
out += '"order":' + this.order.toString();
|
|
181
|
-
out += ',"time":' + this.time.
|
|
182
|
-
out += ',"description":' +
|
|
186
|
+
out += ',"time":' + this.time.toJSON();
|
|
187
|
+
out += ',"description":' + escape(this.description);
|
|
183
188
|
out += ',"depth":' + this.depth.toString();
|
|
184
189
|
out += ',"suites":' + serializeSuites(this.suites);
|
|
185
190
|
out += ',"tests":' + serializeTests(this.tests);
|
|
186
191
|
out += ',"logs":' + serializeLogs(this.logs);
|
|
187
|
-
out += ',"kind":' +
|
|
188
|
-
out += ',"verdict":' +
|
|
192
|
+
out += ',"kind":' + escape(this.kind);
|
|
193
|
+
out += ',"verdict":' + escape(this.verdict);
|
|
189
194
|
out += "}";
|
|
190
195
|
return out;
|
|
191
196
|
}
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
// Whether a beforeEach/afterEach hook should fire for a suite of `kind`. A
|
|
200
|
+
// `null` kinds list (the default) restricts the hook to test cases — the caller
|
|
201
|
+
// passes whether `kind` is one. An explicit list fires for exactly those kinds,
|
|
202
|
+
// which is how `beforeEach(fn, ["describe", "test"])` opts grouping blocks in.
|
|
203
|
+
function hookFiresFor(
|
|
204
|
+
kind: string,
|
|
205
|
+
kinds: string[] | null,
|
|
206
|
+
isTestCaseKind: bool,
|
|
207
|
+
): bool {
|
|
208
|
+
if (kinds === null) return isTestCaseKind;
|
|
209
|
+
for (let i = 0; i < kinds.length; i++) {
|
|
210
|
+
if (unchecked(kinds[i]) == kind) return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
194
215
|
function serializeSuites(values: Suite[]): string {
|
|
195
216
|
if (!values.length) return "[]";
|
|
196
217
|
let out = "[";
|
|
197
218
|
for (let i = 0; i < values.length; i++) {
|
|
198
219
|
if (i) out += ",";
|
|
199
|
-
out += unchecked(values[i]).
|
|
220
|
+
out += unchecked(values[i]).toJSON();
|
|
200
221
|
}
|
|
201
222
|
out += "]";
|
|
202
223
|
return out;
|
|
@@ -207,7 +228,7 @@ function serializeTests(values: Tests[]): string {
|
|
|
207
228
|
let out = "[";
|
|
208
229
|
for (let i = 0; i < values.length; i++) {
|
|
209
230
|
if (i) out += ",";
|
|
210
|
-
out += unchecked(values[i]).
|
|
231
|
+
out += unchecked(values[i]).toJSON();
|
|
211
232
|
}
|
|
212
233
|
out += "]";
|
|
213
234
|
return out;
|
|
@@ -218,7 +239,7 @@ function serializeLogs(values: Log[]): string {
|
|
|
218
239
|
let out = "[";
|
|
219
240
|
for (let i = 0; i < values.length; i++) {
|
|
220
241
|
if (i) out += ",";
|
|
221
|
-
out += unchecked(values[i]).
|
|
242
|
+
out += unchecked(values[i]).toJSON();
|
|
222
243
|
}
|
|
223
244
|
out += "]";
|
|
224
245
|
return out;
|
package/assembly/src/tests.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escape, stringify } from "./stringify";
|
|
2
2
|
|
|
3
3
|
export class Tests {
|
|
4
4
|
public order: i32 = 0;
|
|
@@ -10,24 +10,24 @@ export class Tests {
|
|
|
10
10
|
public message: string = "";
|
|
11
11
|
public location: string = "";
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
toJSON(): string {
|
|
14
14
|
return (
|
|
15
15
|
'{"order":' +
|
|
16
16
|
this.order.toString() +
|
|
17
17
|
',"type":' +
|
|
18
|
-
|
|
18
|
+
escape(this.type) +
|
|
19
19
|
',"verdict":' +
|
|
20
|
-
|
|
20
|
+
escape(this.verdict) +
|
|
21
21
|
',"left":' +
|
|
22
22
|
(this.left.length ? this.left : "null") +
|
|
23
23
|
',"right":' +
|
|
24
24
|
(this.right.length ? this.right : "null") +
|
|
25
25
|
',"instr":' +
|
|
26
|
-
|
|
26
|
+
escape(this.instr) +
|
|
27
27
|
',"message":' +
|
|
28
|
-
|
|
28
|
+
escape(this.message) +
|
|
29
29
|
',"location":' +
|
|
30
|
-
|
|
30
|
+
escape(this.location) +
|
|
31
31
|
"}"
|
|
32
32
|
);
|
|
33
33
|
}
|
package/assembly/util/wipc.ts
CHANGED
package/bin/build-worker-pool.js
CHANGED
|
@@ -24,6 +24,7 @@ export class BuildWorkerPool {
|
|
|
24
24
|
buildCommand: args.buildCommand,
|
|
25
25
|
featureToggles,
|
|
26
26
|
overrides,
|
|
27
|
+
onReads: args.onReads,
|
|
27
28
|
resolve,
|
|
28
29
|
reject,
|
|
29
30
|
});
|
|
@@ -113,6 +114,13 @@ export class BuildWorkerPool {
|
|
|
113
114
|
worker.busy = false;
|
|
114
115
|
worker.task = null;
|
|
115
116
|
if (message.type == "done") {
|
|
117
|
+
if (task.onReads && message.reads?.length) {
|
|
118
|
+
try {
|
|
119
|
+
task.onReads(message.reads);
|
|
120
|
+
} catch {
|
|
121
|
+
// a misbehaving sink shouldn't poison the build pipeline.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
116
124
|
task.resolve();
|
|
117
125
|
} else {
|
|
118
126
|
task.reject(deserializeError(message.error));
|
|
@@ -134,6 +142,7 @@ export class BuildWorkerPool {
|
|
|
134
142
|
modeName: task.modeName,
|
|
135
143
|
featureToggles: task.featureToggles,
|
|
136
144
|
overrides: task.overrides,
|
|
145
|
+
recordReads: !!task.onReads,
|
|
137
146
|
});
|
|
138
147
|
}
|
|
139
148
|
}
|
package/bin/build-worker.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { build } from "./commands/build-core.js";
|
|
2
|
-
process.env.AS_TEST_BUILD_API = "1";
|
|
1
|
+
import { build, buildRecorderStorage } from "./commands/build-core.js";
|
|
3
2
|
process.on("message", async (message) => {
|
|
4
3
|
if (!message || message.type != "build-file") return;
|
|
5
|
-
|
|
4
|
+
// Force the in-process API build path inside this worker so the readFile
|
|
5
|
+
// hook is reachable. We do it on first message rather than at module load
|
|
6
|
+
// so importing this file from a test doesn't mutate the parent env.
|
|
7
|
+
process.env.AS_TEST_BUILD_API = "1";
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const collected = [];
|
|
10
|
+
const runBuild = async () => {
|
|
6
11
|
await build(
|
|
7
12
|
message.configPath,
|
|
8
13
|
[message.file],
|
|
@@ -10,9 +15,28 @@ process.on("message", async (message) => {
|
|
|
10
15
|
message.featureToggles,
|
|
11
16
|
message.overrides,
|
|
12
17
|
);
|
|
18
|
+
};
|
|
19
|
+
try {
|
|
20
|
+
if (message.recordReads) {
|
|
21
|
+
const store = {
|
|
22
|
+
// asc commonly resolves the same source twice during a build (entry
|
|
23
|
+
// lookups, transform passes). Dedupe at record time so IPC payloads
|
|
24
|
+
// stay bounded — `(mode, spec)` is constant for the worker's lifetime
|
|
25
|
+
// of this task, so a file-keyed set is sufficient.
|
|
26
|
+
record: (mode, spec, file) => {
|
|
27
|
+
if (seen.has(file)) return;
|
|
28
|
+
seen.add(file);
|
|
29
|
+
collected.push({ mode, spec, file });
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
await buildRecorderStorage.run(store, runBuild);
|
|
33
|
+
} else {
|
|
34
|
+
await runBuild();
|
|
35
|
+
}
|
|
13
36
|
send({
|
|
14
37
|
type: "done",
|
|
15
38
|
id: message.id,
|
|
39
|
+
reads: message.recordReads ? collected : undefined,
|
|
16
40
|
});
|
|
17
41
|
} catch (error) {
|
|
18
42
|
send({
|