mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 12:19:12 +00:00
fix(ext/node): add assert.CallTracker (#29226)
This commit is contained in:
parent
784be1511b
commit
6bbba3c348
4 changed files with 174 additions and 0 deletions
|
@ -579,6 +579,7 @@ deno_core::extension!(deno_node,
|
|||
"internal_binding/udp_wrap.ts",
|
||||
"internal_binding/util.ts",
|
||||
"internal_binding/uv.ts",
|
||||
"internal/assert/calltracker.js",
|
||||
"internal/assert.mjs",
|
||||
"internal/async_hooks.ts",
|
||||
"internal/blocklist.mjs",
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
} from "ext:deno_node/internal/errors.ts";
|
||||
import { isDeepEqual } from "ext:deno_node/internal/util/comparisons.ts";
|
||||
import { primordials } from "ext:core/mod.js";
|
||||
import { CallTracker } from "ext:deno_node/internal/assert/calltracker.js";
|
||||
import { deprecate } from "node:util";
|
||||
|
||||
const { ObjectPrototypeIsPrototypeOf } = primordials;
|
||||
|
||||
|
@ -882,8 +884,15 @@ function isValidThenable(maybeThennable: any): boolean {
|
|||
return isThenable && typeof maybeThennable !== "function";
|
||||
}
|
||||
|
||||
const CallTracker_ = deprecate(
|
||||
CallTracker,
|
||||
"assert.CallTracker is deprecated.",
|
||||
"DEP0173",
|
||||
);
|
||||
|
||||
Object.assign(strict, {
|
||||
AssertionError,
|
||||
CallTracker: CallTracker_,
|
||||
deepEqual: deepStrictEqual,
|
||||
deepStrictEqual,
|
||||
doesNotMatch,
|
||||
|
@ -906,6 +915,7 @@ Object.assign(strict, {
|
|||
|
||||
export default Object.assign(assert, {
|
||||
AssertionError,
|
||||
CallTracker: CallTracker_,
|
||||
deepEqual,
|
||||
deepStrictEqual,
|
||||
doesNotMatch,
|
||||
|
|
162
ext/node/polyfills/internal/assert/calltracker.js
Normal file
162
ext/node/polyfills/internal/assert/calltracker.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||
|
||||
// deno-lint-ignore-file prefer-primordials
|
||||
|
||||
import { primordials } from "ext:core/mod.js";
|
||||
const {
|
||||
ArrayPrototypePush,
|
||||
ArrayPrototypeSlice,
|
||||
Error,
|
||||
FunctionPrototype,
|
||||
ObjectFreeze,
|
||||
Proxy,
|
||||
ReflectApply,
|
||||
SafeSet,
|
||||
SafeWeakMap,
|
||||
} = primordials;
|
||||
|
||||
import { codes } from "ext:deno_node/internal/errors.ts";
|
||||
const {
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_UNAVAILABLE_DURING_EXIT,
|
||||
} = codes;
|
||||
import { AssertionError } from "ext:deno_node/assertion_error.ts";
|
||||
import { validateUint32 } from "ext:deno_node/internal/validators.mjs";
|
||||
|
||||
const noop = FunctionPrototype;
|
||||
|
||||
class CallTrackerContext {
|
||||
#expected;
|
||||
#calls;
|
||||
#name;
|
||||
#stackTrace;
|
||||
constructor({ expected, stackTrace, name }) {
|
||||
this.#calls = [];
|
||||
this.#expected = expected;
|
||||
this.#stackTrace = stackTrace;
|
||||
this.#name = name;
|
||||
}
|
||||
|
||||
track(thisArg, args) {
|
||||
const argsClone = ObjectFreeze(ArrayPrototypeSlice(args));
|
||||
ArrayPrototypePush(
|
||||
this.#calls,
|
||||
ObjectFreeze({ thisArg, arguments: argsClone }),
|
||||
);
|
||||
}
|
||||
|
||||
get delta() {
|
||||
return this.#calls.length - this.#expected;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#calls = [];
|
||||
}
|
||||
getCalls() {
|
||||
return ObjectFreeze(ArrayPrototypeSlice(this.#calls));
|
||||
}
|
||||
|
||||
report() {
|
||||
if (this.delta !== 0) {
|
||||
const message = `Expected the ${this.#name} function to be ` +
|
||||
`executed ${this.#expected} time(s) but was ` +
|
||||
`executed ${this.#calls.length} time(s).`;
|
||||
return {
|
||||
message,
|
||||
actual: this.#calls.length,
|
||||
expected: this.#expected,
|
||||
operator: this.#name,
|
||||
stack: this.#stackTrace,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CallTracker {
|
||||
#callChecks = new SafeSet();
|
||||
#trackedFunctions = new SafeWeakMap();
|
||||
|
||||
#getTrackedFunction(tracked) {
|
||||
if (!this.#trackedFunctions.has(tracked)) {
|
||||
throw new ERR_INVALID_ARG_VALUE(
|
||||
"tracked",
|
||||
tracked,
|
||||
"is not a tracked function",
|
||||
);
|
||||
}
|
||||
return this.#trackedFunctions.get(tracked);
|
||||
}
|
||||
|
||||
reset(tracked) {
|
||||
if (tracked === undefined) {
|
||||
this.#callChecks.forEach((check) => check.reset());
|
||||
return;
|
||||
}
|
||||
|
||||
this.#getTrackedFunction(tracked).reset();
|
||||
}
|
||||
|
||||
getCalls(tracked) {
|
||||
return this.#getTrackedFunction(tracked).getCalls();
|
||||
}
|
||||
|
||||
calls(fn, expected = 1) {
|
||||
// deno-lint-ignore no-process-global
|
||||
if (process._exiting) {
|
||||
throw new ERR_UNAVAILABLE_DURING_EXIT();
|
||||
}
|
||||
if (typeof fn === "number") {
|
||||
expected = fn;
|
||||
fn = noop;
|
||||
} else if (fn === undefined) {
|
||||
fn = noop;
|
||||
}
|
||||
|
||||
validateUint32(expected, "expected", true);
|
||||
|
||||
const context = new CallTrackerContext({
|
||||
expected,
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
stackTrace: new Error(),
|
||||
name: fn.name || "calls",
|
||||
});
|
||||
const tracked = new Proxy(fn, {
|
||||
__proto__: null,
|
||||
apply(fn, thisArg, argList) {
|
||||
context.track(thisArg, argList);
|
||||
return ReflectApply(fn, thisArg, argList);
|
||||
},
|
||||
});
|
||||
this.#callChecks.add(context);
|
||||
this.#trackedFunctions.set(tracked, context);
|
||||
return tracked;
|
||||
}
|
||||
|
||||
report() {
|
||||
const errors = [];
|
||||
for (const context of this.#callChecks) {
|
||||
const message = context.report();
|
||||
if (message !== undefined) {
|
||||
ArrayPrototypePush(errors, message);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
verify() {
|
||||
const errors = this.report();
|
||||
if (errors.length === 0) {
|
||||
return;
|
||||
}
|
||||
const message = errors.length === 1
|
||||
? errors[0].message
|
||||
: "Functions were not called the expected number of times";
|
||||
throw new AssertionError({
|
||||
message,
|
||||
details: errors,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { CallTracker };
|
|
@ -2682,6 +2682,7 @@ codes.ERR_MULTIPLE_CALLBACK = ERR_MULTIPLE_CALLBACK;
|
|||
codes.ERR_STREAM_WRITE_AFTER_END = ERR_STREAM_WRITE_AFTER_END;
|
||||
codes.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE;
|
||||
codes.ERR_INVALID_ARG_VALUE = ERR_INVALID_ARG_VALUE;
|
||||
codes.ERR_UNAVAILABLE_DURING_EXIT = ERR_UNAVAILABLE_DURING_EXIT;
|
||||
codes.ERR_OUT_OF_RANGE = ERR_OUT_OF_RANGE;
|
||||
codes.ERR_SOCKET_BAD_PORT = ERR_SOCKET_BAD_PORT;
|
||||
codes.ERR_SOCKET_CONNECTION_TIMEOUT = ERR_SOCKET_CONNECTION_TIMEOUT;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue