Skip to content

Commit 2772d30

Browse files
authored
perf: make deno test 10x faster (#20550)
1 parent b9b4ad3 commit 2772d30

File tree

4 files changed

+257
-63
lines changed

4 files changed

+257
-63
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ repository = "https://github.com/denoland/deno"
4040
[workspace.dependencies]
4141
deno_ast = { version = "0.29.3", features = ["transpiling"] }
4242

43-
deno_core = { version = "0.213.0" }
43+
deno_core = { version = "0.214.0" }
4444

4545
deno_runtime = { version = "0.126.0", path = "./runtime" }
4646
napi_sym = { version = "0.48.0", path = "./cli/napi/sym" }

cli/js/40_testing.js

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const {
2222
MapPrototypeSet,
2323
MathCeil,
2424
ObjectKeys,
25-
ObjectHasOwn,
2625
ObjectPrototypeIsPrototypeOf,
2726
Promise,
2827
SafeArrayIterator,
@@ -151,26 +150,14 @@ const OP_DETAILS = {
151150
"op_ws_send_pong": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"],
152151
};
153152

154-
function collectReliableOpMetrics() {
155-
let metrics = core.metrics();
156-
if (metrics.opsDispatched > metrics.opsCompleted) {
157-
// If there are still async ops pending, we drain the event loop to the
158-
// point where all ops that can return `Poll::Ready` have done so, to ensure
159-
// that any ops are ready because of user cleanup code are completed.
160-
const hasPendingWorkerOps = metrics.ops.op_host_recv_message && (
161-
metrics.ops.op_host_recv_message.opsDispatched >
162-
metrics.ops.op_host_recv_message.opsCompleted ||
163-
metrics.ops.op_host_recv_ctrl.opsDispatched >
164-
metrics.ops.op_host_recv_ctrl.opsCompleted
165-
);
166-
return opSanitizerDelay(hasPendingWorkerOps).then(() => {
167-
metrics = core.metrics();
168-
const traces = new Map(core.opCallTraces);
169-
return { metrics, traces };
170-
});
171-
}
172-
const traces = new Map(core.opCallTraces);
173-
return { metrics, traces };
153+
let opIdHostRecvMessage = -1;
154+
let opIdHostRecvCtrl = -1;
155+
let opNames = null;
156+
157+
function populateOpNames() {
158+
opNames = core.ops.op_op_names();
159+
opIdHostRecvMessage = opNames.indexOf("op_host_recv_message");
160+
opIdHostRecvCtrl = opNames.indexOf("op_host_recv_ctrl");
174161
}
175162

176163
// Wrap test function in additional assertion that makes sure
@@ -181,50 +168,61 @@ function collectReliableOpMetrics() {
181168
function assertOps(fn) {
182169
/** @param desc {TestDescription | TestStepDescription} */
183170
return async function asyncOpSanitizer(desc) {
184-
let metrics = collectReliableOpMetrics();
185-
if (metrics.then) {
186-
// We're delaying so await to get the result asynchronously.
187-
metrics = await metrics;
171+
if (opNames === null) populateOpNames();
172+
const res = core.ops.op_test_op_sanitizer_collect(
173+
desc.id,
174+
false,
175+
opIdHostRecvMessage,
176+
opIdHostRecvCtrl,
177+
);
178+
if (res !== 0) {
179+
await opSanitizerDelay(res === 2);
180+
core.ops.op_test_op_sanitizer_collect(
181+
desc.id,
182+
true,
183+
opIdHostRecvMessage,
184+
opIdHostRecvCtrl,
185+
);
188186
}
189-
const { metrics: pre, traces: preTraces } = metrics;
190-
let post;
187+
const preTraces = new Map(core.opCallTraces);
191188
let postTraces;
189+
let report = null;
192190

193191
try {
194192
const innerResult = await fn(desc);
195193
if (innerResult) return innerResult;
196194
} finally {
197-
let metrics = collectReliableOpMetrics();
198-
if (metrics.then) {
199-
// We're delaying so await to get the result asynchronously.
200-
metrics = await metrics;
195+
let res = core.ops.op_test_op_sanitizer_finish(
196+
desc.id,
197+
false,
198+
opIdHostRecvMessage,
199+
opIdHostRecvCtrl,
200+
);
201+
if (res === 1 || res === 2) {
202+
await opSanitizerDelay(res === 2);
203+
res = core.ops.op_test_op_sanitizer_finish(
204+
desc.id,
205+
true,
206+
opIdHostRecvMessage,
207+
opIdHostRecvCtrl,
208+
);
209+
}
210+
postTraces = new Map(core.opCallTraces);
211+
if (res === 3) {
212+
report = core.ops.op_test_op_sanitizer_report(desc.id);
201213
}
202-
({ metrics: post, traces: postTraces } = metrics);
203214
}
204215

205-
// We're checking diff because one might spawn HTTP server in the background
206-
// that will be a pending async op before test starts.
207-
const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync;
208-
const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync;
209-
210-
if (dispatchedDiff === completedDiff) return null;
216+
if (report === null) return null;
211217

212218
const details = [];
213-
for (const key in post.ops) {
214-
if (!ObjectHasOwn(post.ops, key)) {
215-
continue;
216-
}
217-
const preOp = pre.ops[key] ??
218-
{ opsDispatchedAsync: 0, opsCompletedAsync: 0 };
219-
const postOp = post.ops[key];
220-
const dispatchedDiff = postOp.opsDispatchedAsync -
221-
preOp.opsDispatchedAsync;
222-
const completedDiff = postOp.opsCompletedAsync -
223-
preOp.opsCompletedAsync;
224-
225-
if (dispatchedDiff > completedDiff) {
226-
const [name, hint] = OP_DETAILS[key] || [key, null];
227-
const count = dispatchedDiff - completedDiff;
219+
for (const opReport of report) {
220+
const opName = opNames[opReport.id];
221+
const diff = opReport.diff;
222+
223+
if (diff > 0) {
224+
const [name, hint] = OP_DETAILS[opName] || [opName, null];
225+
const count = diff;
228226
let message = `${count} async operation${
229227
count === 1 ? "" : "s"
230228
} to ${name} ${
@@ -234,8 +232,8 @@ function assertOps(fn) {
234232
message += ` This is often caused by not ${hint}.`;
235233
}
236234
const traces = [];
237-
for (const [id, { opName, stack }] of postTraces) {
238-
if (opName !== key) continue;
235+
for (const [id, { opName: traceOpName, stack }] of postTraces) {
236+
if (traceOpName !== opName) continue;
239237
if (MapPrototypeHas(preTraces, id)) continue;
240238
ArrayPrototypePush(traces, stack);
241239
}
@@ -247,9 +245,9 @@ function assertOps(fn) {
247245
message += ArrayPrototypeJoin(traces, "\n\n");
248246
}
249247
ArrayPrototypePush(details, message);
250-
} else if (dispatchedDiff < completedDiff) {
251-
const [name, hint] = OP_DETAILS[key] || [key, null];
252-
const count = completedDiff - dispatchedDiff;
248+
} else if (diff < 0) {
249+
const [name, hint] = OP_DETAILS[opName] || [opName, null];
250+
const count = -diff;
253251
let message = `${count} async operation${
254252
count === 1 ? "" : "s"
255253
} to ${name} ${
@@ -261,8 +259,8 @@ function assertOps(fn) {
261259
message += ` This is often caused by not ${hint}.`;
262260
}
263261
const traces = [];
264-
for (const [id, { opName, stack }] of preTraces) {
265-
if (opName !== key) continue;
262+
for (const [id, { opName: traceOpName, stack }] of preTraces) {
263+
if (opName !== traceOpName) continue;
266264
if (MapPrototypeHas(postTraces, id)) continue;
267265
ArrayPrototypePush(traces, stack);
268266
}
@@ -274,6 +272,8 @@ function assertOps(fn) {
274272
message += ArrayPrototypeJoin(traces, "\n\n");
275273
}
276274
ArrayPrototypePush(details, message);
275+
} else {
276+
throw new Error("unreachable");
277277
}
278278
}
279279

0 commit comments

Comments
 (0)