assertledger 1.4.0 → 1.5.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.fr.md +5 -2
- package/README.md +5 -2
- package/conformance/schema-extensions.json +15 -0
- package/dist/build-info.json +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +213 -8
- package/dist/cli.js.map +1 -1
- package/dist/contracts/index.d.ts +467 -4
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +256 -3
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/qualification.d.ts +528 -0
- package/dist/contracts/qualification.d.ts.map +1 -0
- package/dist/contracts/qualification.js +357 -0
- package/dist/contracts/qualification.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +46 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/qualification.d.ts +17 -0
- package/dist/core/qualification.d.ts.map +1 -0
- package/dist/core/qualification.js +571 -0
- package/dist/core/qualification.js.map +1 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +81 -4
- package/dist/diagnostics.js.map +1 -1
- package/dist/engine/adapters/bun-native.d.ts +35 -0
- package/dist/engine/adapters/bun-native.d.ts.map +1 -0
- package/dist/engine/adapters/bun-native.js +179 -0
- package/dist/engine/adapters/bun-native.js.map +1 -0
- package/dist/engine/adapters/bun-test-designated-profile.d.ts +23 -0
- package/dist/engine/adapters/bun-test-designated-profile.d.ts.map +1 -0
- package/dist/engine/adapters/bun-test-designated-profile.js +24 -0
- package/dist/engine/adapters/bun-test-designated-profile.js.map +1 -0
- package/dist/engine/container.d.ts +7 -0
- package/dist/engine/container.d.ts.map +1 -1
- package/dist/engine/container.js +22 -7
- package/dist/engine/container.js.map +1 -1
- package/dist/engine/git-regression.d.ts.map +1 -1
- package/dist/engine/git-regression.js +9 -2
- package/dist/engine/git-regression.js.map +1 -1
- package/dist/engine/index.d.ts +14 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +828 -97
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/qualification.d.ts +154 -0
- package/dist/engine/qualification.d.ts.map +1 -0
- package/dist/engine/qualification.js +725 -0
- package/dist/engine/qualification.js.map +1 -0
- package/dist/engine/test-closure.d.ts +39 -0
- package/dist/engine/test-closure.d.ts.map +1 -0
- package/dist/engine/test-closure.js +513 -0
- package/dist/engine/test-closure.js.map +1 -0
- package/dist/engine/witness-import.d.ts +31 -0
- package/dist/engine/witness-import.d.ts.map +1 -0
- package/dist/engine/witness-import.js +224 -0
- package/dist/engine/witness-import.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +65 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/sdk/index.d.ts +292 -3
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +39 -1
- package/dist/sdk/index.js.map +1 -1
- package/docs/container-isolation.md +6 -0
- package/docs/migration-orchestration-qualification-v1.md +109 -0
- package/docs/migration-verification-v4.md +94 -0
- package/docs/orchestration-qualification.md +167 -0
- package/docs/reference.md +7 -0
- package/docs/witness-import.md +67 -0
- package/examples/orchestration/README.md +68 -0
- package/examples/orchestration/consumer-obligations.json +101 -0
- package/examples/orchestration/workspace/bitbucket-pipelines.yml +10 -0
- package/examples/orchestration/workspace/command-shape.test.mjs +25 -0
- package/examples/orchestration/workspace/commands.mjs +14 -0
- package/examples/orchestration/workspace/completion.mjs +39 -0
- package/examples/orchestration/workspace/faults.json +1 -0
- package/examples/orchestration/workspace/native.test.ts +4 -0
- package/examples/orchestration/workspace/package.json +6 -0
- package/examples/orchestration/workspace/packages/app/package.json +12 -0
- package/examples/orchestration/workspace/packages/app/verdict.txt +1 -0
- package/examples/orchestration/workspace/packages/leaf/package.json +9 -0
- package/examples/orchestration/workspace/packages/leaf/verdict.txt +1 -0
- package/examples/orchestration/workspace/pnpm-lock.yaml +170 -0
- package/examples/orchestration/workspace/pnpm-workspace.yaml +2 -0
- package/examples/orchestration/workspace/task.mjs +23 -0
- package/examples/orchestration/workspace/turbo.json +38 -0
- package/examples/orchestration/workspace/wrapper.mjs +45 -0
- package/integrations/bun/driver.d.mts +30 -1
- package/integrations/bun/driver.mjs +215 -6
- package/integrations/bun/preload.mjs +186 -5
- package/integrations/bun-native/README.md +86 -0
- package/integrations/bun-native/driver.mjs +396 -0
- package/integrations/bun-native/preload.mjs +375 -0
- package/integrations/skill/SKILL.md +25 -5
- package/package.json +1 -1
- package/schemas/evidence-manifest.v4.json +995 -0
- package/schemas/qualification-execution-request.v1.json +571 -0
- package/schemas/qualification-plan.v1.json +528 -0
- package/schemas/qualification-receipt.v1.json +821 -0
- package/schemas/qualification-replay-result.v1.json +37 -0
- package/schemas/verification-request.v4.json +590 -0
- package/schemas/witness-import-request.v1.json +231 -0
|
@@ -14,11 +14,62 @@ if (!path.isAbsolute(root ?? "")) {
|
|
|
14
14
|
const preloadPath = fileURLToPath(import.meta.url);
|
|
15
15
|
const safeApply = Reflect.apply.bind(Reflect);
|
|
16
16
|
const safeGet = Reflect.get.bind(Reflect);
|
|
17
|
+
const ownDescriptor = Reflect.getOwnPropertyDescriptor.bind(Reflect);
|
|
18
|
+
const prototypeOf = Reflect.getPrototypeOf.bind(Reflect);
|
|
19
|
+
const stringReplace = String.prototype.replace;
|
|
20
|
+
const stringSplit = String.prototype.split;
|
|
21
|
+
const stringTrim = String.prototype.trim;
|
|
22
|
+
const stringStartsWith = String.prototype.startsWith;
|
|
23
|
+
const isErrorInstance = Object.prototype.isPrototypeOf.bind(Error.prototype);
|
|
24
|
+
const ANSI_SGR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "gu");
|
|
25
|
+
const LINE_BREAK = /\r?\n/u;
|
|
26
|
+
const fileKey = (file) =>
|
|
27
|
+
process.platform === "win32" ? path.resolve(file).toLowerCase() : path.resolve(file);
|
|
28
|
+
|
|
29
|
+
// A designated selection runs one existing test ("run") or only loads its file ("load"). Without it
|
|
30
|
+
// the preload keeps the candidate-file behavior: only assertSame failures can be owned.
|
|
31
|
+
const designatedSelection = (() => {
|
|
32
|
+
const value = process.env.ASSERTLEDGER_BUN_DESIGNATED;
|
|
33
|
+
if (value === undefined) return undefined;
|
|
34
|
+
const parsed = JSON.parse(value);
|
|
35
|
+
if (parsed?.mode !== "run" && parsed?.mode !== "load") {
|
|
36
|
+
throw new Error("ASSERTLEDGER_BUN_PRELOAD_CONFIGURATION_INVALID");
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
})();
|
|
40
|
+
const designatedKeys = (
|
|
41
|
+
designatedSelection === undefined
|
|
42
|
+
? []
|
|
43
|
+
: designatedSelection.mode === "run"
|
|
44
|
+
? [designatedSelection.test]
|
|
45
|
+
: designatedSelection.tests
|
|
46
|
+
).map((test) => JSON.stringify([fileKey(path.resolve(root, test.file)), ...test.path]));
|
|
47
|
+
// The expected line may be copied from Bun's output, which prefixes "error:".
|
|
48
|
+
const expectedFailure =
|
|
49
|
+
designatedSelection?.mode === "run" && typeof designatedSelection.expectedFailure === "string"
|
|
50
|
+
? designatedSelection.expectedFailure
|
|
51
|
+
.replace(ANSI_SGR, "")
|
|
52
|
+
.split(LINE_BREAK)[0]
|
|
53
|
+
.trim()
|
|
54
|
+
.replace(/^error:\s*/u, "")
|
|
55
|
+
: null;
|
|
56
|
+
|
|
57
|
+
/** The first line of a failure message without terminal colors, as Bun prints it after "error:". */
|
|
58
|
+
function failureSignature(error) {
|
|
59
|
+
const descriptor = ownDescriptor(error, "message");
|
|
60
|
+
const message = typeof descriptor?.value === "string" ? descriptor.value : "";
|
|
61
|
+
const plain = safeApply(stringReplace, message, [ANSI_SGR, ""]);
|
|
62
|
+
return safeApply(stringTrim, safeApply(stringSplit, plain, [LINE_BREAK])[0] ?? "", []);
|
|
63
|
+
}
|
|
64
|
+
|
|
17
65
|
const rawAssertSame = assertionHelper.assertSame;
|
|
18
66
|
const verifyIssuedError = assertionHelper.isAssertSameFailure;
|
|
19
67
|
const issuedDuringTest = new WeakMap();
|
|
20
68
|
const markIssuingTest = WeakMap.prototype.set.bind(issuedDuringTest);
|
|
21
69
|
const issuingTest = WeakMap.prototype.get.bind(issuedDuringTest);
|
|
70
|
+
const expectIssued = new WeakMap();
|
|
71
|
+
const markExpectIssued = WeakMap.prototype.set.bind(expectIssued);
|
|
72
|
+
const expectIssuingTest = WeakMap.prototype.get.bind(expectIssued);
|
|
22
73
|
const activeTestExecution = new AsyncLocalStorage();
|
|
23
74
|
const currentTestId = AsyncLocalStorage.prototype.getStore.bind(activeTestExecution);
|
|
24
75
|
const runInTest = AsyncLocalStorage.prototype.run.bind(activeTestExecution);
|
|
@@ -31,6 +82,71 @@ function scopedAssertSame(...arguments_) {
|
|
|
31
82
|
throw error;
|
|
32
83
|
}
|
|
33
84
|
}
|
|
85
|
+
|
|
86
|
+
// Only a built-in matcher, present before any test file can extend expect, can issue an owned
|
|
87
|
+
// failure, and only when its message has Bun's matcher form. Usage errors and custom matchers stay
|
|
88
|
+
// ordinary errors.
|
|
89
|
+
const nativeExpect = bunTest.expect;
|
|
90
|
+
const builtInMatchers = new Set();
|
|
91
|
+
const matcherChains = new Set(["not", "resolves", "rejects"]);
|
|
92
|
+
const matcherPrototype =
|
|
93
|
+
designatedSelection === undefined ? null : prototypeOf(nativeExpect(undefined));
|
|
94
|
+
for (const name of matcherPrototype === null ? [] : Reflect.ownKeys(matcherPrototype)) {
|
|
95
|
+
const descriptor = ownDescriptor(matcherPrototype, name);
|
|
96
|
+
if (typeof name === "string" && name !== "constructor" && typeof descriptor?.value === "function")
|
|
97
|
+
builtInMatchers.add(name);
|
|
98
|
+
}
|
|
99
|
+
const hasBuiltInMatcher = Set.prototype.has.bind(builtInMatchers);
|
|
100
|
+
const isMatcherChain = Set.prototype.has.bind(matcherChains);
|
|
101
|
+
function markMatcherFailure(error, id) {
|
|
102
|
+
if (id === undefined || !isErrorInstance(error)) return;
|
|
103
|
+
const signature = failureSignature(error);
|
|
104
|
+
if (safeApply(stringStartsWith, signature, ["expect("]))
|
|
105
|
+
markExpectIssued(error, { id, signature });
|
|
106
|
+
}
|
|
107
|
+
function scopedMatchers(matchers) {
|
|
108
|
+
return new Proxy(matchers, {
|
|
109
|
+
get(target, property) {
|
|
110
|
+
const value = safeGet(target, property, target);
|
|
111
|
+
if (typeof property === "string" && isMatcherChain(property)) return scopedMatchers(value);
|
|
112
|
+
if (typeof value !== "function") return value;
|
|
113
|
+
if (typeof property !== "string" || !hasBuiltInMatcher(property)) return value.bind(target);
|
|
114
|
+
return (...arguments_) => {
|
|
115
|
+
const id = currentTestId();
|
|
116
|
+
try {
|
|
117
|
+
const result = safeApply(value, target, arguments_);
|
|
118
|
+
return result && typeof result.then === "function"
|
|
119
|
+
? Promise.resolve(result).catch((error) => {
|
|
120
|
+
markMatcherFailure(error, id);
|
|
121
|
+
throw error;
|
|
122
|
+
})
|
|
123
|
+
: result;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
markMatcherFailure(error, id);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const scopedExpect = new Proxy(nativeExpect, {
|
|
133
|
+
apply(target, thisArg, arguments_) {
|
|
134
|
+
return scopedMatchers(safeApply(target, thisArg, arguments_));
|
|
135
|
+
},
|
|
136
|
+
get(target, property) {
|
|
137
|
+
const value = safeGet(target, property, target);
|
|
138
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/** Whether a designated test's failure is an owned assertion with the expected first line. */
|
|
143
|
+
function designatedFailureOwned(error, id) {
|
|
144
|
+
const assertSameOwned = verifyIssuedError(error) && issuingTest(error) === id;
|
|
145
|
+
const expectOwned = expectIssuingTest(error)?.id === id;
|
|
146
|
+
if (!assertSameOwned && !expectOwned) return false;
|
|
147
|
+
return expectedFailure === null || failureSignature(error) === expectedFailure;
|
|
148
|
+
}
|
|
149
|
+
|
|
34
150
|
const stringify = JSON.stringify.bind(JSON);
|
|
35
151
|
const evidenceKey = Buffer.alloc(32);
|
|
36
152
|
let received = 0;
|
|
@@ -71,7 +187,19 @@ function registrationFile() {
|
|
|
71
187
|
throw new Error("ASSERTLEDGER_BUN_REGISTRATION_FILE_UNAVAILABLE");
|
|
72
188
|
}
|
|
73
189
|
|
|
74
|
-
|
|
190
|
+
// Describe names enclosing the registration in progress. A templated (`each`) or non-string name
|
|
191
|
+
// can never match a designated path.
|
|
192
|
+
const describeStack = [];
|
|
193
|
+
const UNNAMED = Symbol("unnamed");
|
|
194
|
+
|
|
195
|
+
function designatedIndex(file, name) {
|
|
196
|
+
if (designatedSelection === undefined || describeStack.includes(UNNAMED) || name === UNNAMED) {
|
|
197
|
+
return -1;
|
|
198
|
+
}
|
|
199
|
+
return designatedKeys.indexOf(JSON.stringify([fileKey(file), ...describeStack, name]));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function wrapRegistration(native, cache, templated = false) {
|
|
75
203
|
if (cache.has(native)) return cache.get(native);
|
|
76
204
|
const wrapper = new Proxy(native, {
|
|
77
205
|
apply(target, thisArg, arguments_) {
|
|
@@ -80,14 +208,25 @@ function wrapRegistration(native, cache) {
|
|
|
80
208
|
);
|
|
81
209
|
if (callbackIndex < 0) {
|
|
82
210
|
const result = safeApply(target, thisArg, arguments_);
|
|
83
|
-
return typeof result === "function" ? wrapRegistration(result, cache) : result;
|
|
211
|
+
return typeof result === "function" ? wrapRegistration(result, cache, templated) : result;
|
|
84
212
|
}
|
|
85
213
|
const callback = arguments_[callbackIndex];
|
|
86
214
|
const file = registrationFile();
|
|
215
|
+
const name = !templated && typeof arguments_[0] === "string" ? arguments_[0] : UNNAMED;
|
|
216
|
+
const designated = designatedIndex(file, name);
|
|
217
|
+
if (designatedSelection?.mode === "load") {
|
|
218
|
+
// Loading registers nothing: the file and every module it imports are evaluated only.
|
|
219
|
+
record({ kind: "registered", file, designated });
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
87
222
|
const wrappedArguments = [...arguments_];
|
|
88
223
|
wrappedArguments[callbackIndex] = function (...callbackArguments) {
|
|
89
224
|
const id = randomUUID();
|
|
90
|
-
record(
|
|
225
|
+
record(
|
|
226
|
+
designatedSelection === undefined
|
|
227
|
+
? { kind: "found", id, file }
|
|
228
|
+
: { kind: "found", id, file, designated: designated >= 0 },
|
|
229
|
+
);
|
|
91
230
|
return runInTest(id, () => {
|
|
92
231
|
const passed = (value) => {
|
|
93
232
|
record({ kind: "end", id, status: "pass" });
|
|
@@ -98,7 +237,10 @@ function wrapRegistration(native, cache) {
|
|
|
98
237
|
kind: "end",
|
|
99
238
|
id,
|
|
100
239
|
status: "fail",
|
|
101
|
-
owned:
|
|
240
|
+
owned:
|
|
241
|
+
designatedSelection === undefined
|
|
242
|
+
? verifyIssuedError(error) && issuingTest(error) === id
|
|
243
|
+
: designated >= 0 && designatedFailureOwned(error, id),
|
|
102
244
|
});
|
|
103
245
|
throw error;
|
|
104
246
|
};
|
|
@@ -117,7 +259,42 @@ function wrapRegistration(native, cache) {
|
|
|
117
259
|
get(target, property, receiver) {
|
|
118
260
|
const value = safeGet(target, property, receiver);
|
|
119
261
|
return typeof value === "function" && property !== "constructor"
|
|
120
|
-
? wrapRegistration(value.bind(target), cache)
|
|
262
|
+
? wrapRegistration(value.bind(target), cache, templated || property === "each")
|
|
263
|
+
: value;
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
cache.set(native, wrapper);
|
|
267
|
+
return wrapper;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function wrapDescribe(native, cache, templated = false) {
|
|
271
|
+
if (cache.has(native)) return cache.get(native);
|
|
272
|
+
const wrapper = new Proxy(native, {
|
|
273
|
+
apply(target, thisArg, arguments_) {
|
|
274
|
+
const callbackIndex = arguments_.findIndex(
|
|
275
|
+
(value, index) => typeof value === "function" && (index > 0 || arguments_.length === 1),
|
|
276
|
+
);
|
|
277
|
+
if (callbackIndex < 0) {
|
|
278
|
+
const result = safeApply(target, thisArg, arguments_);
|
|
279
|
+
return typeof result === "function" ? wrapDescribe(result, cache, templated) : result;
|
|
280
|
+
}
|
|
281
|
+
const callback = arguments_[callbackIndex];
|
|
282
|
+
const name = !templated && typeof arguments_[0] === "string" ? arguments_[0] : UNNAMED;
|
|
283
|
+
const wrappedArguments = [...arguments_];
|
|
284
|
+
wrappedArguments[callbackIndex] = function (...callbackArguments) {
|
|
285
|
+
describeStack.push(name);
|
|
286
|
+
try {
|
|
287
|
+
return safeApply(callback, this, callbackArguments);
|
|
288
|
+
} finally {
|
|
289
|
+
describeStack.pop();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
return safeApply(target, thisArg, wrappedArguments);
|
|
293
|
+
},
|
|
294
|
+
get(target, property, receiver) {
|
|
295
|
+
const value = safeGet(target, property, receiver);
|
|
296
|
+
return typeof value === "function" && property !== "constructor"
|
|
297
|
+
? wrapDescribe(value.bind(target), cache, templated || property === "each")
|
|
121
298
|
: value;
|
|
122
299
|
},
|
|
123
300
|
});
|
|
@@ -150,6 +327,7 @@ function wrapHook(nativeHook) {
|
|
|
150
327
|
const cache = new WeakMap();
|
|
151
328
|
const wrappedTest = wrapRegistration(bunTest.test, cache);
|
|
152
329
|
const wrappedIt = wrapRegistration(bunTest.it, cache);
|
|
330
|
+
const describeCache = new WeakMap();
|
|
153
331
|
bunTest.mock.module("bun:test", () => ({
|
|
154
332
|
...bunTest,
|
|
155
333
|
test: wrappedTest,
|
|
@@ -158,6 +336,9 @@ bunTest.mock.module("bun:test", () => ({
|
|
|
158
336
|
afterAll: wrapHook(bunTest.afterAll),
|
|
159
337
|
beforeEach: wrapHook(bunTest.beforeEach),
|
|
160
338
|
afterEach: wrapHook(bunTest.afterEach),
|
|
339
|
+
...(designatedSelection === undefined
|
|
340
|
+
? {}
|
|
341
|
+
: { describe: wrapDescribe(bunTest.describe, describeCache), expect: scopedExpect }),
|
|
161
342
|
}));
|
|
162
343
|
bunTest.mock.module("assertledger/bun", () => ({
|
|
163
344
|
...assertionHelper,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Native Bun collection profile 1.0.0
|
|
2
|
+
|
|
3
|
+
This profile is separate from the frozen helper-only Bun adapter. It requires
|
|
4
|
+
Bun 1.4.2, revision 744846f844374847c902b5e7fd59b4342a51ef99.
|
|
5
|
+
The outer Node process runs the driver with argument arrays and a bounded process
|
|
6
|
+
tree; the driver loads the signed-pipe preload and checks complete JUnit evidence.
|
|
7
|
+
|
|
8
|
+
The outer Node driver exits 0 whenever it emits a complete report, including
|
|
9
|
+
reports of assertion failures or operational inner Bun failures. The report's
|
|
10
|
+
`exitCode` retains the inner Bun exit code and its `outcome` retains the semantic
|
|
11
|
+
classification. Driver exceptions, nonzero or missing outer exit codes, signals,
|
|
12
|
+
process errors, timeouts and truncated output invalidate collection regardless
|
|
13
|
+
of a previously emitted report. This separates transport completion from test
|
|
14
|
+
outcome, including on Windows where forced termination may appear as exit 1
|
|
15
|
+
without a signal. This internal native profile contract does not change the
|
|
16
|
+
frozen helper adapter protocols.
|
|
17
|
+
|
|
18
|
+
The internal native report now always includes `assertionFailureFiles`: the
|
|
19
|
+
sorted, unique relative test-file paths owning issued assertions that failed.
|
|
20
|
+
These paths come from controlled test-registration IDs and failure events, never
|
|
21
|
+
from error stacks or helper locations. An attributed `ASSERTION_FAILURE` report
|
|
22
|
+
requires a nonempty subset of `testFiles`; passing and operational reports carry
|
|
23
|
+
an empty array. Counts, declared files, attribution and inner exit status must
|
|
24
|
+
agree with the outcome. Consumers must use these actual failure owners when
|
|
25
|
+
evaluating a required suite, so a failure in another collected file cannot credit
|
|
26
|
+
a passing suite. This is an additive migration of the unreleased native profile;
|
|
27
|
+
old native reports without this field are refused. Frozen v1-v4 helper reports
|
|
28
|
+
and their schema versions are unchanged.
|
|
29
|
+
|
|
30
|
+
The collector translates driver filesystem keys back to the declared test-file
|
|
31
|
+
names before returning `testFiles` and `assertionFailureFiles`. These outward
|
|
32
|
+
facts use sorted, unique POSIX relative paths and preserve declared case, such as
|
|
33
|
+
`Nested/Example.test.ts`, on every host. Aliased declarations that normalize to
|
|
34
|
+
the same filesystem key, or observations without an unambiguous declared name,
|
|
35
|
+
invalidate collection. Internal driver path normalization remains host-specific.
|
|
36
|
+
|
|
37
|
+
The qualified forms are TypeScript tests importing `bun:test`, `test`, `it`,
|
|
38
|
+
`describe`, async functions, helper-module imports, `test.each`, native `expect`
|
|
39
|
+
matchers and `not`/`resolves`/`rejects` chains. The qualified matcher names are
|
|
40
|
+
`toBe`, `toEqual`, `toStrictEqual`, `toHaveProperty`, and `toThrow`; each has
|
|
41
|
+
executable reference, target and neutral witnesses. Comparison operands and
|
|
42
|
+
arguments must be primitive values or recursively passive plain objects/arrays:
|
|
43
|
+
accessors, functions, proxies, custom prototypes and custom coercions are refused
|
|
44
|
+
before matcher issuance. `toHaveProperty` accepts a simple dotted identifier path
|
|
45
|
+
and an optional passive expected value. `toThrow` accepts a function and an optional
|
|
46
|
+
string expectation, or a rejected native Promise with a plain Error. Callback
|
|
47
|
+
errors are tracked separately from errors issued by the native matcher; active
|
|
48
|
+
returned/thrown values invalidate collection. All other matcher names, asymmetric
|
|
49
|
+
matchers and static expect helpers are explicitly unqualified.
|
|
50
|
+
A matcher failure is attributed through the issuing test's
|
|
51
|
+
async-local identity and an error WeakMap. Test errors are never classified from
|
|
52
|
+
their message or JUnit failure text. Cross-test rethrows, hooks, collection errors,
|
|
53
|
+
timeouts, skipped or missing tests, unmatched events and report inconsistency
|
|
54
|
+
cannot establish a detection. Rejected inputs under `resolves`, TypeError and
|
|
55
|
+
RangeError remain operational failures. Custom matchers (`expect.extend`) are
|
|
56
|
+
explicitly unqualified.
|
|
57
|
+
|
|
58
|
+
Both the outer process timeout and Bun's per-test timeout are operational failures.
|
|
59
|
+
JUnit `TimeoutError` observations invalidate collection even when a synchronous
|
|
60
|
+
callback finishes before the outer budget and throws an owned matcher error after
|
|
61
|
+
its per-test deadline. This observation matches the existing v4 Bun driver's
|
|
62
|
+
timeout handling; its private JUnit parser is not imported as a new dependency.
|
|
63
|
+
|
|
64
|
+
The Node compatibility forms `node:test` default/named registrations with callback
|
|
65
|
+
contexts (`t.after`), generated parameterized registrations, helper imports and
|
|
66
|
+
`node:assert/strict` `ok`, `equal` and `strictEqual` assertions are qualified under
|
|
67
|
+
the pinned Bun revision, with reference/target/neutral witnesses for each name.
|
|
68
|
+
Their operands and optional message must be passive. All other assertion names,
|
|
69
|
+
including callback-bearing `throws` and `rejects`, are explicitly unqualified.
|
|
70
|
+
Native Node AssertionError is attributed only when issued by the wrapped assertion
|
|
71
|
+
function in the current test. Manually constructed errors and failing after hooks
|
|
72
|
+
remain operational failures.
|
|
73
|
+
Snapshots, mocks, global-only registrations, callback-style completion, browser
|
|
74
|
+
tests, and other runtime versions need additional qualification witnesses.
|
|
75
|
+
Successful collection is an observation, not a campaign admission or readiness
|
|
76
|
+
claim.
|
|
77
|
+
|
|
78
|
+
Every declared test file must start at least one test; an empty or missing file
|
|
79
|
+
beside an otherwise passing suite invalidates collection. Successful facts include
|
|
80
|
+
the sorted relative paths of files that actually started tests (`testFiles`).
|
|
81
|
+
The collector exposes binary, driver and preload identities. The consuming
|
|
82
|
+
campaign must bind test/helper/configuration input digests and all world identities.
|
|
83
|
+
The pipe MAC detects accidental or unsophisticated report substitution; this is
|
|
84
|
+
trusted-local, unsandboxed execution. It is not an OS boundary or an authentication
|
|
85
|
+
of a hostile producer. Candidates can execute arbitrary code in that process.
|
|
86
|
+
Replay does not reexecute the tests or authenticate their producer.
|