summaryrefslogtreecommitdiff
path: root/cli/js
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-12-08 13:03:25 +0530
committerGitHub <noreply@github.com>2023-12-08 08:33:25 +0100
commitc5c5dea90debcc5ec53b4803ca530558df32e43f (patch)
tree2bd0483edd0a921802999429d6501ecb59bed3ef /cli/js
parent2235a1a359ffabd72689db58b9af5873e0a9b38a (diff)
chore: use primordials in 40_testing.js (#21422)
This commit brings back usage of primordials in "40_testing.js" by turning it back into an ES module and using new "lazy loading" functionality of ES modules coming from "deno_core". The same approach was applied to "40_jupyter.js". Co-authored-by: Bartek IwaƄczuk <biwanczuk@gmail.com>
Diffstat (limited to 'cli/js')
-rw-r--r--cli/js/40_jupyter.js683
-rw-r--r--cli/js/40_testing.js2324
2 files changed, 1510 insertions, 1497 deletions
diff --git a/cli/js/40_jupyter.js b/cli/js/40_jupyter.js
index bef7f4056..272e4c978 100644
--- a/cli/js/40_jupyter.js
+++ b/cli/js/40_jupyter.js
@@ -36,215 +36,181 @@
* }, { raw: true });
* ```
*/
-(() => {
- const internals = Deno[Deno.internal];
- const core = internals.core;
+import { core, internals } from "ext:core/mod.js";
- const $display = Symbol.for("Jupyter.display");
+const $display = Symbol.for("Jupyter.display");
- /** Escape copied from https://deno.land/std@0.192.0/html/entities.ts */
- const rawToEntityEntries = [
- ["&", "&amp;"],
- ["<", "&lt;"],
- [">", "&gt;"],
- ['"', "&quot;"],
- ["'", "&#39;"],
- ];
+/** Escape copied from https://deno.land/std@0.192.0/html/entities.ts */
+const rawToEntityEntries = [
+ ["&", "&amp;"],
+ ["<", "&lt;"],
+ [">", "&gt;"],
+ ['"', "&quot;"],
+ ["'", "&#39;"],
+];
- const rawToEntity = new Map(rawToEntityEntries);
+const rawToEntity = new Map(rawToEntityEntries);
- const rawRe = new RegExp(`[${[...rawToEntity.keys()].join("")}]`, "g");
+const rawRe = new RegExp(`[${[...rawToEntity.keys()].join("")}]`, "g");
- function escapeHTML(str) {
- return str.replaceAll(
- rawRe,
- (m) => rawToEntity.has(m) ? rawToEntity.get(m) : m,
- );
- }
+function escapeHTML(str) {
+ return str.replaceAll(
+ rawRe,
+ (m) => rawToEntity.has(m) ? rawToEntity.get(m) : m,
+ );
+}
- /** Duck typing our way to common visualization and tabular libraries */
- /** Vegalite */
- function isVegaLike(obj) {
- return obj !== null && typeof obj === "object" && "toSpec" in obj;
+/** Duck typing our way to common visualization and tabular libraries */
+/** Vegalite */
+function isVegaLike(obj) {
+ return obj !== null && typeof obj === "object" && "toSpec" in obj;
+}
+function extractVega(obj) {
+ const spec = obj.toSpec();
+ if (!("$schema" in spec)) {
+ return null;
}
- function extractVega(obj) {
- const spec = obj.toSpec();
- if (!("$schema" in spec)) {
- return null;
- }
- if (typeof spec !== "object") {
- return null;
- }
- let mediaType = "application/vnd.vega.v5+json";
- if (spec.$schema === "https://vega.github.io/schema/vega-lite/v4.json") {
- mediaType = "application/vnd.vegalite.v4+json";
- } else if (
- spec.$schema === "https://vega.github.io/schema/vega-lite/v5.json"
- ) {
- mediaType = "application/vnd.vegalite.v5+json";
- }
- return {
- [mediaType]: spec,
- };
+ if (typeof spec !== "object") {
+ return null;
}
- /** Polars */
- function isDataFrameLike(obj) {
- const isObject = obj !== null && typeof obj === "object";
- if (!isObject) {
- return false;
- }
- const df = obj;
- return df.schema !== void 0 && typeof df.schema === "object" &&
- df.head !== void 0 && typeof df.head === "function" &&
- df.toRecords !== void 0 && typeof df.toRecords === "function";
+ let mediaType = "application/vnd.vega.v5+json";
+ if (spec.$schema === "https://vega.github.io/schema/vega-lite/v4.json") {
+ mediaType = "application/vnd.vegalite.v4+json";
+ } else if (
+ spec.$schema === "https://vega.github.io/schema/vega-lite/v5.json"
+ ) {
+ mediaType = "application/vnd.vegalite.v5+json";
}
- /**
- * Map Polars DataType to JSON Schema data types.
- * @param dataType - The Polars DataType.
- * @returns The corresponding JSON Schema data type.
- */
- function mapPolarsTypeToJSONSchema(colType) {
- const typeMapping = {
- Null: "null",
- Bool: "boolean",
- Int8: "integer",
- Int16: "integer",
- Int32: "integer",
- Int64: "integer",
- UInt8: "integer",
- UInt16: "integer",
- UInt32: "integer",
- UInt64: "integer",
- Float32: "number",
- Float64: "number",
- Date: "string",
- Datetime: "string",
- Utf8: "string",
- Categorical: "string",
- List: "array",
- Struct: "object",
- };
- // These colTypes are weird. When you console.dir or console.log them
- // they show a `DataType` field, however you can't access it directly until you
- // convert it to JSON
- const dataType = colType.toJSON()["DataType"];
- return typeMapping[dataType] || "string";
+ return {
+ [mediaType]: spec,
+ };
+}
+/** Polars */
+function isDataFrameLike(obj) {
+ const isObject = obj !== null && typeof obj === "object";
+ if (!isObject) {
+ return false;
}
+ const df = obj;
+ return df.schema !== void 0 && typeof df.schema === "object" &&
+ df.head !== void 0 && typeof df.head === "function" &&
+ df.toRecords !== void 0 && typeof df.toRecords === "function";
+}
+/**
+ * Map Polars DataType to JSON Schema data types.
+ * @param dataType - The Polars DataType.
+ * @returns The corresponding JSON Schema data type.
+ */
+function mapPolarsTypeToJSONSchema(colType) {
+ const typeMapping = {
+ Null: "null",
+ Bool: "boolean",
+ Int8: "integer",
+ Int16: "integer",
+ Int32: "integer",
+ Int64: "integer",
+ UInt8: "integer",
+ UInt16: "integer",
+ UInt32: "integer",
+ UInt64: "integer",
+ Float32: "number",
+ Float64: "number",
+ Date: "string",
+ Datetime: "string",
+ Utf8: "string",
+ Categorical: "string",
+ List: "array",
+ Struct: "object",
+ };
+ // These colTypes are weird. When you console.dir or console.log them
+ // they show a `DataType` field, however you can't access it directly until you
+ // convert it to JSON
+ const dataType = colType.toJSON()["DataType"];
+ return typeMapping[dataType] || "string";
+}
- function extractDataFrame(df) {
- const fields = [];
- const schema = {
- fields,
- };
- let data = [];
- // Convert DataFrame schema to Tabular DataResource schema
- for (const [colName, colType] of Object.entries(df.schema)) {
- const dataType = mapPolarsTypeToJSONSchema(colType);
- schema.fields.push({
- name: colName,
- type: dataType,
- });
- }
- // Convert DataFrame data to row-oriented JSON
- //
- // TODO(rgbkrk): Determine how to get the polars format max rows
- // Since pl.setTblRows just sets env var POLARS_FMT_MAX_ROWS,
- // we probably just have to pick a number for now.
- //
+function extractDataFrame(df) {
+ const fields = [];
+ const schema = {
+ fields,
+ };
+ let data = [];
+ // Convert DataFrame schema to Tabular DataResource schema
+ for (const [colName, colType] of Object.entries(df.schema)) {
+ const dataType = mapPolarsTypeToJSONSchema(colType);
+ schema.fields.push({
+ name: colName,
+ type: dataType,
+ });
+ }
+ // Convert DataFrame data to row-oriented JSON
+ //
+ // TODO(rgbkrk): Determine how to get the polars format max rows
+ // Since pl.setTblRows just sets env var POLARS_FMT_MAX_ROWS,
+ // we probably just have to pick a number for now.
+ //
- data = df.head(50).toRecords();
- let htmlTable = "<table>";
- htmlTable += "<thead><tr>";
+ data = df.head(50).toRecords();
+ let htmlTable = "<table>";
+ htmlTable += "<thead><tr>";
+ schema.fields.forEach((field) => {
+ htmlTable += `<th>${escapeHTML(String(field.name))}</th>`;
+ });
+ htmlTable += "</tr></thead>";
+ htmlTable += "<tbody>";
+ df.head(10).toRecords().forEach((row) => {
+ htmlTable += "<tr>";
schema.fields.forEach((field) => {
- htmlTable += `<th>${escapeHTML(String(field.name))}</th>`;
- });
- htmlTable += "</tr></thead>";
- htmlTable += "<tbody>";
- df.head(10).toRecords().forEach((row) => {
- htmlTable += "<tr>";
- schema.fields.forEach((field) => {
- htmlTable += `<td>${escapeHTML(String(row[field.name]))}</td>`;
- });
- htmlTable += "</tr>";
+ htmlTable += `<td>${escapeHTML(String(row[field.name]))}</td>`;
});
- htmlTable += "</tbody></table>";
- return {
- "application/vnd.dataresource+json": { data, schema },
- "text/html": htmlTable,
- };
- }
+ htmlTable += "</tr>";
+ });
+ htmlTable += "</tbody></table>";
+ return {
+ "application/vnd.dataresource+json": { data, schema },
+ "text/html": htmlTable,
+ };
+}
- /** Canvas */
- function isCanvasLike(obj) {
- return obj !== null && typeof obj === "object" && "toDataURL" in obj;
- }
+/** Canvas */
+function isCanvasLike(obj) {
+ return obj !== null && typeof obj === "object" && "toDataURL" in obj;
+}
- /** Possible HTML and SVG Elements */
- function isSVGElementLike(obj) {
- return obj !== null && typeof obj === "object" && "outerHTML" in obj &&
- typeof obj.outerHTML === "string" && obj.outerHTML.startsWith("<svg");
- }
+/** Possible HTML and SVG Elements */
+function isSVGElementLike(obj) {
+ return obj !== null && typeof obj === "object" && "outerHTML" in obj &&
+ typeof obj.outerHTML === "string" && obj.outerHTML.startsWith("<svg");
+}
- function isHTMLElementLike(obj) {
- return obj !== null && typeof obj === "object" && "outerHTML" in obj &&
- typeof obj.outerHTML === "string";
- }
+function isHTMLElementLike(obj) {
+ return obj !== null && typeof obj === "object" && "outerHTML" in obj &&
+ typeof obj.outerHTML === "string";
+}
- /** Check to see if an object already contains a `Symbol.for("Jupyter.display") */
- function hasDisplaySymbol(obj) {
- return obj !== null && typeof obj === "object" && $display in obj &&
- typeof obj[$display] === "function";
- }
+/** Check to see if an object already contains a `Symbol.for("Jupyter.display") */
+function hasDisplaySymbol(obj) {
+ return obj !== null && typeof obj === "object" && $display in obj &&
+ typeof obj[$display] === "function";
+}
- function makeDisplayable(obj) {
- return {
- [$display]: () => obj,
- };
- }
+function makeDisplayable(obj) {
+ return {
+ [$display]: () => obj,
+ };
+}
- /**
- * Format an object for displaying in Deno
- *
- * @param obj - The object to be displayed
- * @returns MediaBundle
- */
- async function format(obj) {
- if (hasDisplaySymbol(obj)) {
- return await obj[$display]();
- }
- if (typeof obj !== "object") {
- return {
- "text/plain": Deno[Deno.internal].inspectArgs(["%o", obj], {
- colors: !Deno.noColor,
- }),
- };
- }
-
- if (isCanvasLike(obj)) {
- const dataURL = obj.toDataURL();
- const parts = dataURL.split(",");
- const mime = parts[0].split(":")[1].split(";")[0];
- const data = parts[1];
- return {
- [mime]: data,
- };
- }
- if (isVegaLike(obj)) {
- return extractVega(obj);
- }
- if (isDataFrameLike(obj)) {
- return extractDataFrame(obj);
- }
- if (isSVGElementLike(obj)) {
- return {
- "image/svg+xml": obj.outerHTML,
- };
- }
- if (isHTMLElementLike(obj)) {
- return {
- "text/html": obj.outerHTML,
- };
- }
+/**
+ * Format an object for displaying in Deno
+ *
+ * @param obj - The object to be displayed
+ * @returns MediaBundle
+ */
+async function format(obj) {
+ if (hasDisplaySymbol(obj)) {
+ return await obj[$display]();
+ }
+ if (typeof obj !== "object") {
return {
"text/plain": Deno[Deno.internal].inspectArgs(["%o", obj], {
colors: !Deno.noColor,
@@ -252,180 +218,211 @@
};
}
- /**
- * This function creates a tagged template function for a given media type.
- * The tagged template function takes a template string and returns a displayable object.
- *
- * @param mediatype - The media type for the tagged template function.
- * @returns A function that takes a template string and returns a displayable object.
- */
- function createTaggedTemplateDisplayable(mediatype) {
- return (strings, ...values) => {
- const payload = strings.reduce(
- (acc, string, i) =>
- acc + string + (values[i] !== undefined ? values[i] : ""),
- "",
- );
- return makeDisplayable({ [mediatype]: payload });
+ if (isCanvasLike(obj)) {
+ const dataURL = obj.toDataURL();
+ const parts = dataURL.split(",");
+ const mime = parts[0].split(":")[1].split(";")[0];
+ const data = parts[1];
+ return {
+ [mime]: data,
+ };
+ }
+ if (isVegaLike(obj)) {
+ return extractVega(obj);
+ }
+ if (isDataFrameLike(obj)) {
+ return extractDataFrame(obj);
+ }
+ if (isSVGElementLike(obj)) {
+ return {
+ "image/svg+xml": obj.outerHTML,
};
}
+ if (isHTMLElementLike(obj)) {
+ return {
+ "text/html": obj.outerHTML,
+ };
+ }
+ return {
+ "text/plain": Deno[Deno.internal].inspectArgs(["%o", obj], {
+ colors: !Deno.noColor,
+ }),
+ };
+}
- /**
- * Show Markdown in Jupyter frontends with a tagged template function.
- *
- * Takes a template string and returns a displayable object for Jupyter frontends.
- *
- * @example
- * Create a Markdown view.
- *
- * ```typescript
- * md`# Notebooks in TypeScript via Deno ![Deno logo](https://github.com/denoland.png?size=32)
- *
- * * TypeScript ${Deno.version.typescript}
- * * V8 ${Deno.version.v8}
- * * Deno ${Deno.version.deno}
- *
- * Interactive compute with Jupyter _built into Deno_!
- * `
- * ```
- */
- const md = createTaggedTemplateDisplayable("text/markdown");
+/**
+ * This function creates a tagged template function for a given media type.
+ * The tagged template function takes a template string and returns a displayable object.
+ *
+ * @param mediatype - The media type for the tagged template function.
+ * @returns A function that takes a template string and returns a displayable object.
+ */
+function createTaggedTemplateDisplayable(mediatype) {
+ return (strings, ...values) => {
+ const payload = strings.reduce(
+ (acc, string, i) =>
+ acc + string + (values[i] !== undefined ? values[i] : ""),
+ "",
+ );
+ return makeDisplayable({ [mediatype]: payload });
+ };
+}
- /**
- * Show HTML in Jupyter frontends with a tagged template function.
- *
- * Takes a template string and returns a displayable object for Jupyter frontends.
- *
- * @example
- * Create an HTML view.
- * ```typescript
- * html`<h1>Hello, world!</h1>`
- * ```
- */
- const html = createTaggedTemplateDisplayable("text/html");
- /**
- * SVG Tagged Template Function.
- *
- * Takes a template string and returns a displayable object for Jupyter frontends.
- *
- * Example usage:
- *
- * svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
- * <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
- * </svg>`
- */
- const svg = createTaggedTemplateDisplayable("image/svg+xml");
+/**
+ * Show Markdown in Jupyter frontends with a tagged template function.
+ *
+ * Takes a template string and returns a displayable object for Jupyter frontends.
+ *
+ * @example
+ * Create a Markdown view.
+ *
+ * ```typescript
+ * md`# Notebooks in TypeScript via Deno ![Deno logo](https://github.com/denoland.png?size=32)
+ *
+ * * TypeScript ${Deno.version.typescript}
+ * * V8 ${Deno.version.v8}
+ * * Deno ${Deno.version.deno}
+ *
+ * Interactive compute with Jupyter _built into Deno_!
+ * `
+ * ```
+ */
+const md = createTaggedTemplateDisplayable("text/markdown");
+
+/**
+ * Show HTML in Jupyter frontends with a tagged template function.
+ *
+ * Takes a template string and returns a displayable object for Jupyter frontends.
+ *
+ * @example
+ * Create an HTML view.
+ * ```typescript
+ * html`<h1>Hello, world!</h1>`
+ * ```
+ */
+const html = createTaggedTemplateDisplayable("text/html");
+/**
+ * SVG Tagged Template Function.
+ *
+ * Takes a template string and returns a displayable object for Jupyter frontends.
+ *
+ * Example usage:
+ *
+ * svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
+ * <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
+ * </svg>`
+ */
+const svg = createTaggedTemplateDisplayable("image/svg+xml");
- function isMediaBundle(obj) {
- if (obj == null || typeof obj !== "object" || Array.isArray(obj)) {
+function isMediaBundle(obj) {
+ if (obj == null || typeof obj !== "object" || Array.isArray(obj)) {
+ return false;
+ }
+ for (const key in obj) {
+ if (typeof key !== "string") {
return false;
}
- for (const key in obj) {
- if (typeof key !== "string") {
- return false;
- }
- }
- return true;
}
+ return true;
+}
- async function formatInner(obj, raw) {
- if (raw && isMediaBundle(obj)) {
- return obj;
- } else {
- return await format(obj);
- }
+async function formatInner(obj, raw) {
+ if (raw && isMediaBundle(obj)) {
+ return obj;
+ } else {
+ return await format(obj);
}
+}
- internals.jupyter = { formatInner };
+internals.jupyter = { formatInner };
- function enableJupyter() {
- const {
- op_jupyter_broadcast,
- } = core.ensureFastOps();
+function enableJupyter() {
+ const {
+ op_jupyter_broadcast,
+ } = core.ensureFastOps();
- async function broadcast(
- msgType,
- content,
- { metadata = {}, buffers = [] } = {},
- ) {
- await op_jupyter_broadcast(msgType, content, metadata, buffers);
- }
+ async function broadcast(
+ msgType,
+ content,
+ { metadata = {}, buffers = [] } = {},
+ ) {
+ await op_jupyter_broadcast(msgType, content, metadata, buffers);
+ }
- async function broadcastResult(executionCount, result) {
- try {
- if (result === undefined) {
- return;
- }
+ async function broadcastResult(executionCount, result) {
+ try {
+ if (result === undefined) {
+ return;
+ }
- const data = await format(result);
- await broadcast("execute_result", {
- execution_count: executionCount,
- data,
- metadata: {},
+ const data = await format(result);
+ await broadcast("execute_result", {
+ execution_count: executionCount,
+ data,
+ metadata: {},
+ });
+ } catch (err) {
+ if (err instanceof Error) {
+ const stack = err.stack || "";
+ await broadcast("error", {
+ ename: err.name,
+ evalue: err.message,
+ traceback: stack.split("\n"),
+ });
+ } else if (typeof err == "string") {
+ await broadcast("error", {
+ ename: "Error",
+ evalue: err,
+ traceback: [],
+ });
+ } else {
+ await broadcast("error", {
+ ename: "Error",
+ evalue:
+ "An error occurred while formatting a result, but it could not be identified",
+ traceback: [],
});
- } catch (err) {
- if (err instanceof Error) {
- const stack = err.stack || "";
- await broadcast("error", {
- ename: err.name,
- evalue: err.message,
- traceback: stack.split("\n"),
- });
- } else if (typeof err == "string") {
- await broadcast("error", {
- ename: "Error",
- evalue: err,
- traceback: [],
- });
- } else {
- await broadcast("error", {
- ename: "Error",
- evalue:
- "An error occurred while formatting a result, but it could not be identified",
- traceback: [],
- });
- }
}
}
+ }
- internals.jupyter.broadcastResult = broadcastResult;
+ internals.jupyter.broadcastResult = broadcastResult;
- /**
- * Display function for Jupyter Deno Kernel.
- * Mimics the behavior of IPython's `display(obj, raw=True)` function to allow
- * asynchronous displaying of objects in Jupyter.
- *
- * @param obj - The object to be displayed
- * @param options - Display options
- */
- async function display(obj, options = { raw: false, update: false }) {
- const bundle = await formatInner(obj, options.raw);
- let messageType = "display_data";
- if (options.update) {
- messageType = "update_display_data";
- }
- let transient = {};
- if (options.display_id) {
- transient = { display_id: options.display_id };
- }
- await broadcast(messageType, {
- data: bundle,
- metadata: {},
- transient,
- });
- return;
+ /**
+ * Display function for Jupyter Deno Kernel.
+ * Mimics the behavior of IPython's `display(obj, raw=True)` function to allow
+ * asynchronous displaying of objects in Jupyter.
+ *
+ * @param obj - The object to be displayed
+ * @param options - Display options
+ */
+ async function display(obj, options = { raw: false, update: false }) {
+ const bundle = await formatInner(obj, options.raw);
+ let messageType = "display_data";
+ if (options.update) {
+ messageType = "update_display_data";
}
-
- globalThis.Deno.jupyter = {
- broadcast,
- display,
- format,
- md,
- html,
- svg,
- $display,
- };
+ let transient = {};
+ if (options.display_id) {
+ transient = { display_id: options.display_id };
+ }
+ await broadcast(messageType, {
+ data: bundle,
+ metadata: {},
+ transient,
+ });
+ return;
}
- internals.enableJupyter = enableJupyter;
-})();
+ globalThis.Deno.jupyter = {
+ broadcast,
+ display,
+ format,
+ md,
+ html,
+ svg,
+ $display,
+ };
+}
+
+internals.enableJupyter = enableJupyter;
diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js
index f878d59a4..5b51ac169 100644
--- a/cli/js/40_testing.js
+++ b/cli/js/40_testing.js
@@ -1,67 +1,84 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
-// Do not use primordials because we do not want to depend on the __bootstrap
-// namespace.
+import { core, internals, primordials } from "ext:core/mod.js";
+const ops = core.ops;
+
+import { setExitHandler } from "ext:runtime/30_os.js";
+import { Console } from "ext:deno_console/01_console.js";
+import { serializePermissions } from "ext:runtime/10_permissions.js";
+import { setTimeout } from "ext:deno_web/02_timers.js";
+
+const {
+ ArrayPrototypeFilter,
+ ArrayPrototypeJoin,
+ ArrayPrototypePush,
+ ArrayPrototypeShift,
+ DateNow,
+ Error,
+ FunctionPrototype,
+ Map,
+ MapPrototypeGet,
+ MapPrototypeHas,
+ MapPrototypeSet,
+ MathCeil,
+ ObjectKeys,
+ ObjectPrototypeIsPrototypeOf,
+ Promise,
+ SafeArrayIterator,
+ Set,
+ StringPrototypeReplaceAll,
+ SymbolToStringTag,
+ TypeError,
+} = primordials;
+
+const opSanitizerDelayResolveQueue = [];
+let hasSetOpSanitizerDelayMacrotask = false;
+
+// Even if every resource is closed by the end of a test, there can be a delay
+// until the pending ops have all finished. This function returns a promise
+// that resolves when it's (probably) fine to run the op sanitizer.
//
-// deno-lint-ignore-file
-(() => {
- const internals = Deno[Deno.internal];
- const core = internals.core;
- const ops = core.ops;
-
- const {
- setExitHandler,
- Console,
- serializePermissions,
- } = internals;
-
- const opSanitizerDelayResolveQueue = [];
- let hasSetOpSanitizerDelayMacrotask = false;
-
- // Even if every resource is closed by the end of a test, there can be a delay
- // until the pending ops have all finished. This function returns a promise
- // that resolves when it's (probably) fine to run the op sanitizer.
- //
- // This is implemented by adding a macrotask callback that runs after the
- // all ready async ops resolve, and the timer macrotask. Using just a macrotask
- // callback without delaying is sufficient, because when the macrotask callback
- // runs after async op dispatch, we know that all async ops that can currently
- // return `Poll::Ready` have done so, and have been dispatched to JS.
- //
- // Worker ops are an exception to this, because there is no way for the user to
- // await shutdown of the worker from the thread calling `worker.terminate()`.
- // Because of this, we give extra leeway for worker ops to complete, by waiting
- // for a whole millisecond if there are pending worker ops.
- function opSanitizerDelay(hasPendingWorkerOps) {
- if (!hasSetOpSanitizerDelayMacrotask) {
- core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask);
- hasSetOpSanitizerDelayMacrotask = true;
- }
- const p = new Promise((resolve) => {
- // Schedule an async op to complete immediately to ensure the macrotask is
- // run. We rely on the fact that enqueueing the resolver callback during the
- // timeout callback will mean that the resolver gets called in the same
- // event loop tick as the timeout callback.
- setTimeout(() => {
- opSanitizerDelayResolveQueue.push(resolve);
- }, hasPendingWorkerOps ? 1 : 0);
- });
- return p;
+// This is implemented by adding a macrotask callback that runs after the
+// all ready async ops resolve, and the timer macrotask. Using just a macrotask
+// callback without delaying is sufficient, because when the macrotask callback
+// runs after async op dispatch, we know that all async ops that can currently
+// return `Poll::Ready` have done so, and have been dispatched to JS.
+//
+// Worker ops are an exception to this, because there is no way for the user to
+// await shutdown of the worker from the thread calling `worker.terminate()`.
+// Because of this, we give extra leeway for worker ops to complete, by waiting
+// for a whole millisecond if there are pending worker ops.
+function opSanitizerDelay(hasPendingWorkerOps) {
+ if (!hasSetOpSanitizerDelayMacrotask) {
+ core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask);
+ hasSetOpSanitizerDelayMacrotask = true;
}
-
- function handleOpSanitizerDelayMacrotask() {
- const resolve = opSanitizerDelayResolveQueue.shift();
- if (resolve) {
- resolve();
- return opSanitizerDelayResolveQueue.length === 0;
- }
- return undefined; // we performed no work, so can skip microtasks checkpoint
+ const p = new Promise((resolve) => {
+ // Schedule an async op to complete immediately to ensure the macrotask is
+ // run. We rely on the fact that enqueueing the resolver callback during the
+ // timeout callback will mean that the resolver gets called in the same
+ // event loop tick as the timeout callback.
+ setTimeout(() => {
+ ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve);
+ }, hasPendingWorkerOps ? 1 : 0);
+ });
+ return p;
+}
+
+function handleOpSanitizerDelayMacrotask() {
+ const resolve = ArrayPrototypeShift(opSanitizerDelayResolveQueue);
+ if (resolve) {
+ resolve();
+ return opSanitizerDelayResolveQueue.length === 0;
}
+ return undefined; // we performed no work, so can skip microtasks checkpoint
+}
- // An async operation to $0 was started in this test, but never completed. This is often caused by not $1.
- // An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test.
- // deno-fmt-ignore
- const OP_DETAILS = {
+// An async operation to $0 was started in this test, but never completed. This is often caused by not $1.
+// An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test.
+// deno-fmt-ignore
+const OP_DETAILS = {
"op_blob_read_part": ["read from a Blob or File", "awaiting the result of a Blob or File read"],
"op_broadcast_recv": ["receive a message from a BroadcastChannel", "closing the BroadcastChannel"],
"op_broadcast_send": ["send a message to a BroadcastChannel", "closing the BroadcastChannel"],
@@ -135,840 +152,888 @@
"op_ws_send_pong": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"],
};
- let opIdHostRecvMessage = -1;
- let opIdHostRecvCtrl = -1;
- let opNames = null;
-
- function populateOpNames() {
- opNames = core.ops.op_op_names();
- opIdHostRecvMessage = opNames.indexOf("op_host_recv_message");
- opIdHostRecvCtrl = opNames.indexOf("op_host_recv_ctrl");
- }
+let opIdHostRecvMessage = -1;
+let opIdHostRecvCtrl = -1;
+let opNames = null;
+
+function populateOpNames() {
+ opNames = core.ops.op_op_names();
+ opIdHostRecvMessage = opNames.indexOf("op_host_recv_message");
+ opIdHostRecvCtrl = opNames.indexOf("op_host_recv_ctrl");
+}
+
+// Wrap test function in additional assertion that makes sure
+// the test case does not leak async "ops" - ie. number of async
+// completed ops after the test is the same as number of dispatched
+// ops. Note that "unref" ops are ignored since in nature that are
+// optional.
+function assertOps(fn) {
+ /** @param desc {TestDescription | TestStepDescription} */
+ return async function asyncOpSanitizer(desc) {
+ if (opNames === null) populateOpNames();
+ const res = core.ops.op_test_op_sanitizer_collect(
+ desc.id,
+ false,
+ opIdHostRecvMessage,
+ opIdHostRecvCtrl,
+ );
+ if (res !== 0) {
+ await opSanitizerDelay(res === 2);
+ core.ops.op_test_op_sanitizer_collect(
+ desc.id,
+ true,
+ opIdHostRecvMessage,
+ opIdHostRecvCtrl,
+ );
+ }
+ const preTraces = new Map(core.opCallTraces);
+ let postTraces;
+ let report = null;
- // Wrap test function in additional assertion that makes sure
- // the test case does not leak async "ops" - ie. number of async
- // completed ops after the test is the same as number of dispatched
- // ops. Note that "unref" ops are ignored since in nature that are
- // optional.
- function assertOps(fn) {
- /** @param desc {TestDescription | TestStepDescription} */
- return async function asyncOpSanitizer(desc) {
- if (opNames === null) populateOpNames();
- const res = core.ops.op_test_op_sanitizer_collect(
+ try {
+ const innerResult = await fn(desc);
+ if (innerResult) return innerResult;
+ } finally {
+ let res = core.ops.op_test_op_sanitizer_finish(
desc.id,
false,
opIdHostRecvMessage,
opIdHostRecvCtrl,
);
- if (res !== 0) {
+ if (res === 1 || res === 2) {
await opSanitizerDelay(res === 2);
- core.ops.op_test_op_sanitizer_collect(
+ res = core.ops.op_test_op_sanitizer_finish(
desc.id,
true,
opIdHostRecvMessage,
opIdHostRecvCtrl,
);
}
- const preTraces = new Map(core.opCallTraces);
- let postTraces;
- let report = null;
-
- try {
- const innerResult = await fn(desc);
- if (innerResult) return innerResult;
- } finally {
- let res = core.ops.op_test_op_sanitizer_finish(
- desc.id,
- false,
- opIdHostRecvMessage,
- opIdHostRecvCtrl,
- );
- if (res === 1 || res === 2) {
- await opSanitizerDelay(res === 2);
- res = core.ops.op_test_op_sanitizer_finish(
- desc.id,
- true,
- opIdHostRecvMessage,
- opIdHostRecvCtrl,
- );
- }
- postTraces = new Map(core.opCallTraces);
- if (res === 3) {
- report = core.ops.op_test_op_sanitizer_report(desc.id);
- }
+ postTraces = new Map(core.opCallTraces);
+ if (res === 3) {
+ report = core.ops.op_test_op_sanitizer_report(desc.id);
}
+ }
- if (report === null) return null;
-
- const details = [];
- for (const opReport of report) {
- const opName = opNames[opReport.id];
- const diff = opReport.diff;
-
- if (diff > 0) {
- const [name, hint] = OP_DETAILS[opName] || [opName, null];
- const count = diff;
- let message = `${count} async operation${
- count === 1 ? "" : "s"
- } to ${name} ${
- count === 1 ? "was" : "were"
- } started in this test, but never completed.`;
- if (hint) {
- message += ` This is often caused by not ${hint}.`;
- }
- const traces = [];
- for (const [id, { opName: traceOpName, stack }] of postTraces) {
- if (traceOpName !== opName) continue;
- if (preTraces.has(id)) continue;
- traces.push(stack);
- }
- if (traces.length === 1) {
- message += " The operation was started here:\n";
- message += traces[0];
- } else if (traces.length > 1) {
- message += " The operations were started here:\n";
- message += traces.join("\n\n");
- }
- details.push(message);
- } else if (diff < 0) {
- const [name, hint] = OP_DETAILS[opName] || [opName, null];
- const count = -diff;
- let message = `${count} async operation${
- count === 1 ? "" : "s"
- } to ${name} ${
- count === 1 ? "was" : "were"
- } started before this test, but ${
- count === 1 ? "was" : "were"
- } completed during the test. Async operations should not complete in a test if they were not started in that test.`;
- if (hint) {
- message += ` This is often caused by not ${hint}.`;
- }
- const traces = [];
- for (const [id, { opName: traceOpName, stack }] of preTraces) {
- if (opName !== traceOpName) continue;
- if (postTraces.has(id)) continue;
- traces.push(stack);
- }
- if (traces.length === 1) {
- message += " The operation was started here:\n";
- message += traces[0];
- } else if (traces.length > 1) {
- message += " The operations were started here:\n";
- message += traces.join("\n\n");
- }
- details.push(message);
- } else {
- throw new Error("unreachable");
+ if (report === null) return null;
+
+ const details = [];
+ for (const opReport of report) {
+ const opName = opNames[opReport.id];
+ const diff = opReport.diff;
+
+ if (diff > 0) {
+ const [name, hint] = OP_DETAILS[opName] || [opName, null];
+ const count = diff;
+ let message = `${count} async operation${
+ count === 1 ? "" : "s"
+ } to ${name} ${
+ count === 1 ? "was" : "were"
+ } started in this test, but never completed.`;
+ if (hint) {
+ message += ` This is often caused by not ${hint}.`;
+ }
+ const traces = [];
+ for (const [id, { opName: traceOpName, stack }] of postTraces) {
+ if (traceOpName !== opName) continue;
+ if (MapPrototypeHas(preTraces, id)) continue;
+ ArrayPrototypePush(traces, stack);
}
+ if (traces.length === 1) {
+ message += " The operation was started here:\n";
+ message += traces[0];
+ } else if (traces.length > 1) {
+ message += " The operations were started here:\n";
+ message += ArrayPrototypeJoin(traces, "\n\n");
+ }
+ ArrayPrototypePush(details, message);
+ } else if (diff < 0) {
+ const [name, hint] = OP_DETAILS[opName] || [opName, null];
+ const count = -diff;
+ let message = `${count} async operation${
+ count === 1 ? "" : "s"
+ } to ${name} ${
+ count === 1 ? "was" : "were"
+ } started before this test, but ${
+ count === 1 ? "was" : "were"
+ } completed during the test. Async operations should not complete in a test if they were not started in that test.`;
+ if (hint) {
+ message += ` This is often caused by not ${hint}.`;
+ }
+ const traces = [];
+ for (const [id, { opName: traceOpName, stack }] of preTraces) {
+ if (opName !== traceOpName) continue;
+ if (MapPrototypeHas(postTraces, id)) continue;
+ ArrayPrototypePush(traces, stack);
+ }
+ if (traces.length === 1) {
+ message += " The operation was started here:\n";
+ message += traces[0];
+ } else if (traces.length > 1) {
+ message += " The operations were started here:\n";
+ message += ArrayPrototypeJoin(traces, "\n\n");
+ }
+ ArrayPrototypePush(details, message);
+ } else {
+ throw new Error("unreachable");
}
+ }
- return {
- failed: { leakedOps: [details, core.isOpCallTracingEnabled()] },
- };
+ return {
+ failed: { leakedOps: [details, core.isOpCallTracingEnabled()] },
};
+ };
+}
+
+function prettyResourceNames(name) {
+ switch (name) {
+ case "fsFile":
+ return ["A file", "opened", "closed"];
+ case "fetchRequest":
+ return ["A fetch request", "started", "finished"];
+ case "fetchRequestBody":
+ return ["A fetch request body", "created", "closed"];
+ case "fetchResponse":
+ return ["A fetch response body", "created", "consumed"];
+ case "httpClient":
+ return ["An HTTP client", "created", "closed"];
+ case "dynamicLibrary":
+ return ["A dynamic library", "loaded", "unloaded"];
+ case "httpConn":
+ return ["An inbound HTTP connection", "accepted", "closed"];
+ case "httpStream":
+ return ["An inbound HTTP request", "accepted", "closed"];
+ case "tcpStream":
+ return ["A TCP connection", "opened/accepted", "closed"];
+ case "unixStream":
+ return ["A Unix connection", "opened/accepted", "closed"];
+ case "tlsStream":
+ return ["A TLS connection", "opened/accepted", "closed"];
+ case "tlsListener":
+ return ["A TLS listener", "opened", "closed"];
+ case "unixListener":
+ return ["A Unix listener", "opened", "closed"];
+ case "unixDatagram":
+ return ["A Unix datagram", "opened", "closed"];
+ case "tcpListener":
+ return ["A TCP listener", "opened", "closed"];
+ case "udpSocket":
+ return ["A UDP socket", "opened", "closed"];
+ case "timer":
+ return ["A timer", "started", "fired/cleared"];
+ case "textDecoder":
+ return ["A text decoder", "created", "finished"];
+ case "messagePort":
+ return ["A message port", "created", "closed"];
+ case "webSocketStream":
+ return ["A WebSocket", "opened", "closed"];
+ case "fsEvents":
+ return ["A file system watcher", "created", "closed"];
+ case "childStdin":
+ return ["A child process stdin", "opened", "closed"];
+ case "childStdout":
+ return ["A child process stdout", "opened", "closed"];
+ case "childStderr":
+ return ["A child process stderr", "opened", "closed"];
+ case "child":
+ return ["A child process", "started", "closed"];
+ case "signal":
+ return ["A signal listener", "created", "fired/cleared"];
+ case "stdin":
+ return ["The stdin pipe", "opened", "closed"];
+ case "stdout":
+ return ["The stdout pipe", "opened", "closed"];
+ case "stderr":
+ return ["The stderr pipe", "opened", "closed"];
+ case "compression":
+ return ["A CompressionStream", "created", "closed"];
+ default:
+ return [`A "${name}" resource`, "created", "cleaned up"];
}
-
- function prettyResourceNames(name) {
- switch (name) {
- case "fsFile":
- return ["A file", "opened", "closed"];
- case "fetchRequest":
- return ["A fetch request", "started", "finished"];
- case "fetchRequestBody":
- return ["A fetch request body", "created", "closed"];
- case "fetchResponse":
- return ["A fetch response body", "created", "consumed"];
- case "httpClient":
- return ["An HTTP client", "created", "closed"];
- case "dynamicLibrary":
- return ["A dynamic library", "loaded", "unloaded"];
- case "httpConn":
- return ["An inbound HTTP connection", "accepted", "closed"];
- case "httpStream":
- return ["An inbound HTTP request", "accepted", "closed"];
- case "tcpStream":
- return ["A TCP connection", "opened/accepted", "closed"];
- case "unixStream":
- return ["A Unix connection", "opened/accepted", "closed"];
- case "tlsStream":
- return ["A TLS connection", "opened/accepted", "closed"];
- case "tlsListener":
- return ["A TLS listener", "opened", "closed"];
- case "unixListener":
- return ["A Unix listener", "opened", "closed"];
- case "unixDatagram":
- return ["A Unix datagram", "opened", "closed"];
- case "tcpListener":
- return ["A TCP listener", "opened", "closed"];
- case "udpSocket":
- return ["A UDP socket", "opened", "closed"];
- case "timer":
- return ["A timer", "started", "fired/cleared"];
- case "textDecoder":
- return ["A text decoder", "created", "finished"];
- case "messagePort":
- return ["A message port", "created", "closed"];
- case "webSocketStream":
- return ["A WebSocket", "opened", "closed"];
- case "fsEvents":
- return ["A file system watcher", "created", "closed"];
- case "childStdin":
- return ["A child process stdin", "opened", "closed"];
- case "childStdout":
- return ["A child process stdout", "opened", "closed"];
- case "childStderr":
- return ["A child process stderr", "opened", "closed"];
- case "child":
- return ["A child process", "started", "closed"];
- case "signal":
- return ["A signal listener", "created", "fired/cleared"];
- case "stdin":
- return ["The stdin pipe", "opened", "closed"];
- case "stdout":
- return ["The stdout pipe", "opened", "closed"];
- case "stderr":
- return ["The stderr pipe", "opened", "closed"];
- case "compression":
- return ["A CompressionStream", "created", "closed"];
- default:
- return [`A "${name}" resource`, "created", "cleaned up"];
- }
+}
+
+function resourceCloseHint(name) {
+ switch (name) {
+ case "fsFile":
+ return "Close the file handle by calling `file.close()`.";
+ case "fetchRequest":
+ return "Await the promise returned from `fetch()` or abort the fetch with an abort signal.";
+ case "fetchRequestBody":
+ return "Terminate the request body `ReadableStream` by closing or erroring it.";
+ case "fetchResponse":
+ return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`.";
+ case "httpClient":
+ return "Close the HTTP client by calling `httpClient.close()`.";
+ case "dynamicLibrary":
+ return "Unload the dynamic library by calling `dynamicLibrary.close()`.";
+ case "httpConn":
+ return "Close the inbound HTTP connection by calling `httpConn.close()`.";
+ case "httpStream":
+ return "Close the inbound HTTP request by responding with `e.respondWith()` or closing the HTTP connection.";
+ case "tcpStream":
+ return "Close the TCP connection by calling `tcpConn.close()`.";
+ case "unixStream":
+ return "Close the Unix socket connection by calling `unixConn.close()`.";
+ case "tlsStream":
+ return "Close the TLS connection by calling `tlsConn.close()`.";
+ case "tlsListener":
+ return "Close the TLS listener by calling `tlsListener.close()`.";
+ case "unixListener":
+ return "Close the Unix socket listener by calling `unixListener.close()`.";
+ case "unixDatagram":
+ return "Close the Unix datagram socket by calling `unixDatagram.close()`.";
+ case "tcpListener":
+ return "Close the TCP listener by calling `tcpListener.close()`.";
+ case "udpSocket":
+ return "Close the UDP socket by calling `udpSocket.close()`.";
+ case "timer":
+ return "Clear the timer by calling `clearInterval` or `clearTimeout`.";
+ case "textDecoder":
+ return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`.";
+ case "messagePort":
+ return "Close the message port by calling `messagePort.close()`.";
+ case "webSocketStream":
+ return "Close the WebSocket by calling `webSocket.close()`.";
+ case "fsEvents":
+ return "Close the file system watcher by calling `watcher.close()`.";
+ case "childStdin":
+ return "Close the child process stdin by calling `proc.stdin.close()`.";
+ case "childStdout":
+ return "Close the child process stdout by calling `proc.stdout.close()` or `await child.stdout.cancel()`.";
+ case "childStderr":
+ return "Close the child process stderr by calling `proc.stderr.close()` or `await child.stderr.cancel()`.";
+ case "child":
+ return "Close the child process by calling `proc.kill()` or `proc.close()`.";
+ case "signal":
+ return "Clear the signal listener by calling `Deno.removeSignalListener`.";
+ case "stdin":
+ return "Close the stdin pipe by calling `Deno.stdin.close()`.";
+ case "stdout":
+ return "Close the stdout pipe by calling `Deno.stdout.close()`.";
+ case "stderr":
+ return "Close the stderr pipe by calling `Deno.stderr.close()`.";
+ case "compression":
+ return "Close the compression stream by calling `await stream.writable.close()`.";
+ default:
+ return "Close the resource before the end of the test.";
}
+}
- function resourceCloseHint(name) {
- switch (name) {
- case "fsFile":
- return "Close the file handle by calling `file.close()`.";
- case "fetchRequest":
- return "Await the promise returned from `fetch()` or abort the fetch with an abort signal.";
- case "fetchRequestBody":
- return "Terminate the request body `ReadableStream` by closing or erroring it.";
- case "fetchResponse":
- return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`.";
- case "httpClient":
- return "Close the HTTP client by calling `httpClient.close()`.";
- case "dynamicLibrary":
- return "Unload the dynamic library by calling `dynamicLibrary.close()`.";
- case "httpConn":
- return "Close the inbound HTTP connection by calling `httpConn.close()`.";
- case "httpStream":
- return "Close the inbound HTTP request by responding with `e.respondWith()` or closing the HTTP connection.";
- case "tcpStream":
- return "Close the TCP connection by calling `tcpConn.close()`.";
- case "unixStream":
- return "Close the Unix socket connection by calling `unixConn.close()`.";
- case "tlsStream":
- return "Close the TLS connection by calling `tlsConn.close()`.";
- case "tlsListener":
- return "Close the TLS listener by calling `tlsListener.close()`.";
- case "unixListener":
- return "Close the Unix socket listener by calling `unixListener.close()`.";
- case "unixDatagram":
- return "Close the Unix datagram socket by calling `unixDatagram.close()`.";
- case "tcpListener":
- return "Close the TCP listener by calling `tcpListener.close()`.";
- case "udpSocket":
- return "Close the UDP socket by calling `udpSocket.close()`.";
- case "timer":
- return "Clear the timer by calling `clearInterval` or `clearTimeout`.";
- case "textDecoder":
- return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`.";
- case "messagePort":
- return "Close the message port by calling `messagePort.close()`.";
- case "webSocketStream":
- return "Close the WebSocket by calling `webSocket.close()`.";
- case "fsEvents":
- return "Close the file system watcher by calling `watcher.close()`.";
- case "childStdin":
- return "Close the child process stdin by calling `proc.stdin.close()`.";
- case "childStdout":
- return "Close the child process stdout by calling `proc.stdout.close()` or `await child.stdout.cancel()`.";
- case "childStderr":
- return "Close the child process stderr by calling `proc.stderr.close()` or `await child.stderr.cancel()`.";
- case "child":
- return "Close the child process by calling `proc.kill()` or `proc.close()`.";
- case "signal":
- return "Clear the signal listener by calling `Deno.removeSignalListener`.";
- case "stdin":
- return "Close the stdin pipe by calling `Deno.stdin.close()`.";
- case "stdout":
- return "Close the stdout pipe by calling `Deno.stdout.close()`.";
- case "stderr":
- return "Close the stderr pipe by calling `Deno.stderr.close()`.";
- case "compression":
- return "Close the compression stream by calling `await stream.writable.close()`.";
- default:
- return "Close the resource before the end of the test.";
+// Wrap test function in additional assertion that makes sure
+// the test case does not "leak" resources - ie. resource table after
+// the test has exactly the same contents as before the test.
+function assertResources(fn) {
+ /** @param desc {TestDescription | TestStepDescription} */
+ return async function resourceSanitizer(desc) {
+ const pre = core.resources();
+ const innerResult = await fn(desc);
+ if (innerResult) return innerResult;
+ const post = core.resources();
+
+ const allResources = new Set([
+ ...new SafeArrayIterator(ObjectKeys(pre)),
+ ...new SafeArrayIterator(ObjectKeys(post)),
+ ]);
+
+ const details = [];
+ for (const resource of allResources) {
+ const preResource = pre[resource];
+ const postResource = post[resource];
+ if (preResource === postResource) continue;
+
+ if (preResource === undefined) {
+ const [name, action1, action2] = prettyResourceNames(postResource);
+ const hint = resourceCloseHint(postResource);
+ const detail =
+ `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`;
+ ArrayPrototypePush(details, detail);
+ } else {
+ const [name, action1, action2] = prettyResourceNames(preResource);
+ const detail =
+ `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`;
+ ArrayPrototypePush(details, detail);
+ }
}
- }
+ if (details.length == 0) {
+ return null;
+ }
+ return { failed: { leakedResources: details } };
+ };
+}
+
+// Wrap test function in additional assertion that makes sure
+// that the test case does not accidentally exit prematurely.
+function assertExit(fn, isTest) {
+ return async function exitSanitizer(...params) {
+ setExitHandler((exitCode) => {
+ throw new Error(
+ `${
+ isTest ? "Test case" : "Bench"
+ } attempted to exit with exit code: ${exitCode}`,
+ );
+ });
- // Wrap test function in additional assertion that makes sure
- // the test case does not "leak" resources - ie. resource table after
- // the test has exactly the same contents as before the test.
- function assertResources(fn) {
- /** @param desc {TestDescription | TestStepDescription} */
- return async function resourceSanitizer(desc) {
- const pre = core.resources();
- const innerResult = await fn(desc);
+ try {
+ const innerResult = await fn(...new SafeArrayIterator(params));
if (innerResult) return innerResult;
- const post = core.resources();
-
- const allResources = new Set([
- ...Object.keys(pre),
- ...Object.keys(post),
- ]);
-
- const details = [];
- for (const resource of allResources) {
- const preResource = pre[resource];
- const postResource = post[resource];
- if (preResource === postResource) continue;
-
- if (preResource === undefined) {
- const [name, action1, action2] = prettyResourceNames(postResource);
- const hint = resourceCloseHint(postResource);
- const detail =
- `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`;
- details.push(detail);
- } else {
- const [name, action1, action2] = prettyResourceNames(preResource);
- const detail =
- `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`;
- details.push(detail);
- }
- }
- if (details.length == 0) {
- return null;
- }
- return { failed: { leakedResources: details } };
- };
- }
-
- // Wrap test function in additional assertion that makes sure
- // that the test case does not accidentally exit prematurely.
- function assertExit(fn, isTest) {
- return async function exitSanitizer(...params) {
- setExitHandler((exitCode) => {
- throw new Error(
- `${
- isTest ? "Test case" : "Bench"
- } attempted to exit with exit code: ${exitCode}`,
- );
- });
+ } finally {
+ setExitHandler(null);
+ }
+ };
+}
- try {
- const innerResult = await fn(...params);
- if (innerResult) return innerResult;
- } finally {
- setExitHandler(null);
+function wrapOuter(fn, desc) {
+ return async function outerWrapped() {
+ try {
+ if (desc.ignore) {
+ return "ignored";
}
- };
- }
-
- function wrapOuter(fn, desc) {
- return async function outerWrapped() {
- try {
- if (desc.ignore) {
- return "ignored";
- }
- return await fn(desc) ?? "ok";
- } catch (error) {
- return { failed: { jsError: core.destructureError(error) } };
- } finally {
- const state = testStates.get(desc.id);
- for (const childDesc of state.children) {
- stepReportResult(childDesc, { failed: "incomplete" }, 0);
- }
- state.completed = true;
+ return await fn(desc) ?? "ok";
+ } catch (error) {
+ return { failed: { jsError: core.destructureError(error) } };
+ } finally {
+ const state = MapPrototypeGet(testStates, desc.id);
+ for (const childDesc of state.children) {
+ stepReportResult(childDesc, { failed: "incomplete" }, 0);
}
- };
- }
+ state.completed = true;
+ }
+ };
+}
- function wrapInner(fn) {
- /** @param desc {TestDescription | TestStepDescription} */
- return async function innerWrapped(desc) {
- function getRunningStepDescs() {
- const results = [];
- let childDesc = desc;
- while (childDesc.parent != null) {
- const state = testStates.get(childDesc.parent.id);
- for (const siblingDesc of state.children) {
- if (siblingDesc.id == childDesc.id) {
- continue;
- }
- const siblingState = testStates.get(siblingDesc.id);
- if (!siblingState.completed) {
- results.push(siblingDesc);
- }
+function wrapInner(fn) {
+ /** @param desc {TestDescription | TestStepDescription} */
+ return async function innerWrapped(desc) {
+ function getRunningStepDescs() {
+ const results = [];
+ let childDesc = desc;
+ while (childDesc.parent != null) {
+ const state = MapPrototypeGet(testStates, childDesc.parent.id);
+ for (const siblingDesc of state.children) {
+ if (siblingDesc.id == childDesc.id) {
+ continue;
+ }
+ const siblingState = MapPrototypeGet(testStates, siblingDesc.id);
+ if (!siblingState.completed) {
+ ArrayPrototypePush(results, siblingDesc);
}
- childDesc = childDesc.parent;
- }
- return results;
- }
- const runningStepDescs = getRunningStepDescs();
- const runningStepDescsWithSanitizers = runningStepDescs.filter(
- (d) => usesSanitizer(d),
- );
-
- if (runningStepDescsWithSanitizers.length > 0) {
- return {
- failed: {
- overlapsWithSanitizers: runningStepDescsWithSanitizers.map(
- getFullName,
- ),
- },
- };
- }
-
- if (usesSanitizer(desc) && runningStepDescs.length > 0) {
- return {
- failed: {
- hasSanitizersAndOverlaps: runningStepDescs.map(getFullName),
- },
- };
- }
- await fn(testStates.get(desc.id).context);
- let failedSteps = 0;
- for (const childDesc of testStates.get(desc.id).children) {
- const state = testStates.get(childDesc.id);
- if (!state.completed) {
- return { failed: "incompleteSteps" };
- }
- if (state.failed) {
- failedSteps++;
}
+ childDesc = childDesc.parent;
}
- return failedSteps == 0 ? null : { failed: { failedSteps } };
- };
- }
-
- function pledgePermissions(permissions) {
- return ops.op_pledge_test_permissions(
- serializePermissions(permissions),
+ return results;
+ }
+ const runningStepDescs = getRunningStepDescs();
+ const runningStepDescsWithSanitizers = ArrayPrototypeFilter(
+ runningStepDescs,
+ (d) => usesSanitizer(d),
);
- }
- function restorePermissions(token) {
- ops.op_restore_test_permissions(token);
- }
-
- function withPermissions(fn, permissions) {
- return async function applyPermissions(...params) {
- const token = pledgePermissions(permissions);
+ if (runningStepDescsWithSanitizers.length > 0) {
+ return {
+ failed: {
+ overlapsWithSanitizers: runningStepDescsWithSanitizers.map(
+ getFullName,
+ ),
+ },
+ };
+ }
- try {
- return await fn(...params);
- } finally {
- restorePermissions(token);
+ if (usesSanitizer(desc) && runningStepDescs.length > 0) {
+ return {
+ failed: {
+ hasSanitizersAndOverlaps: runningStepDescs.map(getFullName),
+ },
+ };
+ }
+ await fn(MapPrototypeGet(testStates, desc.id).context);
+ let failedSteps = 0;
+ for (const childDesc of MapPrototypeGet(testStates, desc.id).children) {
+ const state = MapPrototypeGet(testStates, childDesc.id);
+ if (!state.completed) {
+ return { failed: "incompleteSteps" };
}
- };
- }
-
- const ESCAPE_ASCII_CHARS = [
- ["\b", "\\b"],
- ["\f", "\\f"],
- ["\t", "\\t"],
- ["\n", "\\n"],
- ["\r", "\\r"],
- ["\v", "\\v"],
- ];
-
- /**
- * @param {string} name
- * @returns {string}
- */
- function escapeName(name) {
- // Check if we need to escape a character
- for (let i = 0; i < name.length; i++) {
- const ch = name.charCodeAt(i);
- if (ch <= 13 && ch >= 8) {
- // Slow path: We do need to escape it
- for (const [escape, replaceWith] of ESCAPE_ASCII_CHARS) {
- name = name.replaceAll(escape, replaceWith);
- }
- return name;
+ if (state.failed) {
+ failedSteps++;
}
}
-
- // We didn't need to escape anything, return original string
- return name;
+ return failedSteps == 0 ? null : { failed: { failedSteps } };
+ };
+}
+
+function pledgePermissions(permissions) {
+ return ops.op_pledge_test_permissions(
+ serializePermissions(permissions),
+ );
+}
+
+function restorePermissions(token) {
+ ops.op_restore_test_permissions(token);
+}
+
+function withPermissions(fn, permissions) {
+ return async function applyPermissions(...params) {
+ const token = pledgePermissions(permissions);
+
+ try {
+ return await fn(...new SafeArrayIterator(params));
+ } finally {
+ restorePermissions(token);
+ }
+ };
+}
+
+const ESCAPE_ASCII_CHARS = [
+ ["\b", "\\b"],
+ ["\f", "\\f"],
+ ["\t", "\\t"],
+ ["\n", "\\n"],
+ ["\r", "\\r"],
+ ["\v", "\\v"],
+];
+
+/**
+ * @param {string} name
+ * @returns {string}
+ */
+function escapeName(name) {
+ // Check if we need to escape a character
+ for (let i = 0; i < name.length; i++) {
+ const ch = name.charCodeAt(i);
+ if (ch <= 13 && ch >= 8) {
+ // Slow path: We do need to escape it
+ for (const [escape, replaceWith] of ESCAPE_ASCII_CHARS) {
+ name = StringPrototypeReplaceAll(name, escape, replaceWith);
+ }
+ return name;
+ }
}
- /**
- * @typedef {{
- * id: number,
- * name: string,
- * fn: TestFunction
- * origin: string,
- * location: TestLocation,
- * ignore: boolean,
- * only: boolean.
- * sanitizeOps: boolean,
- * sanitizeResources: boolean,
- * sanitizeExit: boolean,
- * permissions: PermissionOptions,
- * }} TestDescription
- *
- * @typedef {{
- * id: number,
- * name: string,
- * fn: TestFunction
- * origin: string,
- * location: TestLocation,
- * ignore: boolean,
- * level: number,
- * parent: TestDescription | TestStepDescription,
- * rootId: number,
- * rootName: String,
- * sanitizeOps: boolean,
- * sanitizeResources: boolean,
- * sanitizeExit: boolean,
- * }} TestStepDescription
- *
- * @typedef {{
- * context: TestContext,
- * children: TestStepDescription[],
- * completed: boolean,
- * }} TestState
- *
- * @typedef {{
- * context: TestContext,
- * children: TestStepDescription[],
- * completed: boolean,
- * failed: boolean,
- * }} TestStepState
- *
- * @typedef {{
- * id: number,
- * name: string,
- * fn: BenchFunction
- * origin: string,
- * ignore: boolean,
- * only: boolean.
- * sanitizeExit: boolean,
- * permissions: PermissionOptions,
- * }} BenchDescription
- */
-
- /** @type {Map<number, TestState | TestStepState>} */
- const testStates = new Map();
- /** @type {number | null} */
- let currentBenchId = null;
- // These local variables are used to track time measurements at
- // `BenchContext::{start,end}` calls. They are global instead of using a state
- // map to minimise the overhead of assigning them.
- /** @type {number | null} */
- let currentBenchUserExplicitStart = null;
- /** @type {number | null} */
- let currentBenchUserExplicitEnd = null;
-
- const registerTestIdRetBuf = new Uint32Array(1);
- const registerTestIdRetBufU8 = new Uint8Array(registerTestIdRetBuf.buffer);
-
- function testInner(
- nameOrFnOrOptions,
- optionsOrFn,
- maybeFn,
- overrides = {},
- ) {
- if (typeof ops.op_register_test != "function") {
- return;
- }
+ // We didn't need to escape anything, return original string
+ return name;
+}
+
+/**
+ * @typedef {{
+ * id: number,
+ * name: string,
+ * fn: TestFunction
+ * origin: string,
+ * location: TestLocation,
+ * ignore: boolean,
+ * only: boolean.
+ * sanitizeOps: boolean,
+ * sanitizeResources: boolean,
+ * sanitizeExit: boolean,
+ * permissions: PermissionOptions,
+ * }} TestDescription
+ *
+ * @typedef {{
+ * id: number,
+ * name: string,
+ * fn: TestFunction
+ * origin: string,
+ * location: TestLocation,
+ * ignore: boolean,
+ * level: number,
+ * parent: TestDescription | TestStepDescription,
+ * rootId: number,
+ * rootName: String,
+ * sanitizeOps: boolean,
+ * sanitizeResources: boolean,
+ * sanitizeExit: boolean,
+ * }} TestStepDescription
+ *
+ * @typedef {{
+ * context: TestContext,
+ * children: TestStepDescription[],
+ * completed: boolean,
+ * }} TestState
+ *
+ * @typedef {{
+ * context: TestContext,
+ * children: TestStepDescription[],
+ * completed: boolean,
+ * failed: boolean,
+ * }} TestStepState
+ *
+ * @typedef {{
+ * id: number,
+ * name: string,
+ * fn: BenchFunction
+ * origin: string,
+ * ignore: boolean,
+ * only: boolean.
+ * sanitizeExit: boolean,
+ * permissions: PermissionOptions,
+ * }} BenchDescription
+ */
+
+/** @type {Map<number, TestState | TestStepState>} */
+const testStates = new Map();
+/** @type {number | null} */
+let currentBenchId = null;
+// These local variables are used to track time measurements at
+// `BenchContext::{start,end}` calls. They are global instead of using a state
+// map to minimise the overhead of assigning them.
+/** @type {number | null} */
+let currentBenchUserExplicitStart = null;
+/** @type {number | null} */
+let currentBenchUserExplicitEnd = null;
+
+const registerTestIdRetBuf = new Uint32Array(1);
+const registerTestIdRetBufU8 = new Uint8Array(registerTestIdRetBuf.buffer);
+
+function testInner(
+ nameOrFnOrOptions,
+ optionsOrFn,
+ maybeFn,
+ overrides = {},
+) {
+ if (typeof ops.op_register_test != "function") {
+ return;
+ }
- let testDesc;
- const defaults = {
- ignore: false,
- only: false,
- sanitizeOps: true,
- sanitizeResources: true,
- sanitizeExit: true,
- permissions: null,
- };
+ let testDesc;
+ const defaults = {
+ ignore: false,
+ only: false,
+ sanitizeOps: true,
+ sanitizeResources: true,
+ sanitizeExit: true,
+ permissions: null,
+ };
- if (typeof nameOrFnOrOptions === "string") {
- if (!nameOrFnOrOptions) {
- throw new TypeError("The test name can't be empty");
- }
- if (typeof optionsOrFn === "function") {
- testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
- } else {
- if (!maybeFn || typeof maybeFn !== "function") {
- throw new TypeError("Missing test function");
- }
- if (optionsOrFn.fn != undefined) {
- throw new TypeError(
- "Unexpected 'fn' field in options, test function is already provided as the third argument.",
- );
- }
- if (optionsOrFn.name != undefined) {
- throw new TypeError(
- "Unexpected 'name' field in options, test name is already provided as the first argument.",
- );
- }
- testDesc = {
- ...defaults,
- ...optionsOrFn,
- fn: maybeFn,
- name: nameOrFnOrOptions,
- };
- }
- } else if (typeof nameOrFnOrOptions === "function") {
- if (!nameOrFnOrOptions.name) {
- throw new TypeError("The test function must have a name");
+ if (typeof nameOrFnOrOptions === "string") {
+ if (!nameOrFnOrOptions) {
+ throw new TypeError("The test name can't be empty");
+ }
+ if (typeof optionsOrFn === "function") {
+ testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
+ } else {
+ if (!maybeFn || typeof maybeFn !== "function") {
+ throw new TypeError("Missing test function");
}
- if (optionsOrFn != undefined) {
- throw new TypeError("Unexpected second argument to Deno.test()");
+ if (optionsOrFn.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, test function is already provided as the third argument.",
+ );
}
- if (maybeFn != undefined) {
- throw new TypeError("Unexpected third argument to Deno.test()");
+ if (optionsOrFn.name != undefined) {
+ throw new TypeError(
+ "Unexpected 'name' field in options, test name is already provided as the first argument.",
+ );
}
testDesc = {
...defaults,
- fn: nameOrFnOrOptions,
- name: nameOrFnOrOptions.name,
+ ...optionsOrFn,
+ fn: maybeFn,
+ name: nameOrFnOrOptions,
};
- } else {
- let fn;
- let name;
- if (typeof optionsOrFn === "function") {
- fn = optionsOrFn;
- if (nameOrFnOrOptions.fn != undefined) {
- throw new TypeError(
- "Unexpected 'fn' field in options, test function is already provided as the second argument.",
- );
- }
- name = nameOrFnOrOptions.name ?? fn.name;
- } else {
- if (
- !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
- ) {
- throw new TypeError(
- "Expected 'fn' field in the first argument to be a test function.",
- );
- }
- fn = nameOrFnOrOptions.fn;
- name = nameOrFnOrOptions.name ?? fn.name;
+ }
+ } else if (typeof nameOrFnOrOptions === "function") {
+ if (!nameOrFnOrOptions.name) {
+ throw new TypeError("The test function must have a name");
+ }
+ if (optionsOrFn != undefined) {
+ throw new TypeError("Unexpected second argument to Deno.test()");
+ }
+ if (maybeFn != undefined) {
+ throw new TypeError("Unexpected third argument to Deno.test()");
+ }
+ testDesc = {
+ ...defaults,
+ fn: nameOrFnOrOptions,
+ name: nameOrFnOrOptions.name,
+ };
+ } else {
+ let fn;
+ let name;
+ if (typeof optionsOrFn === "function") {
+ fn = optionsOrFn;
+ if (nameOrFnOrOptions.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, test function is already provided as the second argument.",
+ );
}
- if (!name) {
- throw new TypeError("The test name can't be empty");
+ name = nameOrFnOrOptions.name ?? fn.name;
+ } else {
+ if (
+ !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
+ ) {
+ throw new TypeError(
+ "Expected 'fn' field in the first argument to be a test function.",
+ );
}
- testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
+ fn = nameOrFnOrOptions.fn;
+ name = nameOrFnOrOptions.name ?? fn.name;
}
-
- testDesc = { ...testDesc, ...overrides };
-
- // Delete this prop in case the user passed it. It's used to detect steps.
- delete testDesc.parent;
-
- testDesc.location = core.currentUserCallSite();
- testDesc.fn = wrapTest(testDesc);
- testDesc.name = escapeName(testDesc.name);
-
- const origin = ops.op_register_test(
- testDesc.fn,
- testDesc.name,
- testDesc.ignore,
- testDesc.only,
- testDesc.location.fileName,
- testDesc.location.lineNumber,
- testDesc.location.columnNumber,
- registerTestIdRetBufU8,
- );
- testDesc.id = registerTestIdRetBuf[0];
- testDesc.origin = origin;
- testStates.set(testDesc.id, {
- context: createTestContext(testDesc),
- children: [],
- completed: false,
- });
+ if (!name) {
+ throw new TypeError("The test name can't be empty");
+ }
+ testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
}
- // Main test function provided by Deno.
- function test(
- nameOrFnOrOptions,
- optionsOrFn,
- maybeFn,
- ) {
- return testInner(nameOrFnOrOptions, optionsOrFn, maybeFn);
+ testDesc = { ...testDesc, ...overrides };
+
+ // Delete this prop in case the user passed it. It's used to detect steps.
+ delete testDesc.parent;
+
+ testDesc.location = core.currentUserCallSite();
+ testDesc.fn = wrapTest(testDesc);
+ testDesc.name = escapeName(testDesc.name);
+
+ const origin = ops.op_register_test(
+ testDesc.fn,
+ testDesc.name,
+ testDesc.ignore,
+ testDesc.only,
+ testDesc.location.fileName,
+ testDesc.location.lineNumber,
+ testDesc.location.columnNumber,
+ registerTestIdRetBufU8,
+ );
+ testDesc.id = registerTestIdRetBuf[0];
+ testDesc.origin = origin;
+ MapPrototypeSet(testStates, testDesc.id, {
+ context: createTestContext(testDesc),
+ children: [],
+ completed: false,
+ });
+}
+
+// Main test function provided by Deno.
+function test(
+ nameOrFnOrOptions,
+ optionsOrFn,
+ maybeFn,
+) {
+ return testInner(nameOrFnOrOptions, optionsOrFn, maybeFn);
+}
+
+test.ignore = function (nameOrFnOrOptions, optionsOrFn, maybeFn) {
+ return testInner(nameOrFnOrOptions, optionsOrFn, maybeFn, { ignore: true });
+};
+
+test.only = function (
+ nameOrFnOrOptions,
+ optionsOrFn,
+ maybeFn,
+) {
+ return testInner(nameOrFnOrOptions, optionsOrFn, maybeFn, { only: true });
+};
+
+let registeredWarmupBench = false;
+
+// Main bench function provided by Deno.
+function bench(
+ nameOrFnOrOptions,
+ optionsOrFn,
+ maybeFn,
+) {
+ if (typeof ops.op_register_bench != "function") {
+ return;
}
- test.ignore = function (nameOrFnOrOptions, optionsOrFn, maybeFn) {
- return testInner(nameOrFnOrOptions, optionsOrFn, maybeFn, { ignore: true });
- };
-
- test.only = function (
- nameOrFnOrOptions,
- optionsOrFn,
- maybeFn,
- ) {
- return testInner(nameOrFnOrOptions, optionsOrFn, maybeFn, { only: true });
- };
-
- let registeredWarmupBench = false;
-
- // Main bench function provided by Deno.
- function bench(
- nameOrFnOrOptions,
- optionsOrFn,
- maybeFn,
- ) {
- if (typeof ops.op_register_bench != "function") {
- return;
- }
-
- if (!registeredWarmupBench) {
- registeredWarmupBench = true;
- const warmupBenchDesc = {
- name: "<warmup>",
- fn: function warmup() {},
- async: false,
- ignore: false,
- baseline: false,
- only: false,
- sanitizeExit: true,
- permissions: null,
- warmup: true,
- };
- warmupBenchDesc.fn = wrapBenchmark(warmupBenchDesc);
- const { id, origin } = ops.op_register_bench(warmupBenchDesc);
- warmupBenchDesc.id = id;
- warmupBenchDesc.origin = origin;
- }
-
- let benchDesc;
- const defaults = {
+ if (!registeredWarmupBench) {
+ registeredWarmupBench = true;
+ const warmupBenchDesc = {
+ name: "<warmup>",
+ fn: function warmup() {},
+ async: false,
ignore: false,
baseline: false,
only: false,
sanitizeExit: true,
permissions: null,
+ warmup: true,
};
+ warmupBenchDesc.fn = wrapBenchmark(warmupBenchDesc);
+ const { id, origin } = ops.op_register_bench(warmupBenchDesc);
+ warmupBenchDesc.id = id;
+ warmupBenchDesc.origin = origin;
+ }
- if (typeof nameOrFnOrOptions === "string") {
- if (!nameOrFnOrOptions) {
- throw new TypeError("The bench name can't be empty");
- }
- if (typeof optionsOrFn === "function") {
- benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
- } else {
- if (!maybeFn || typeof maybeFn !== "function") {
- throw new TypeError("Missing bench function");
- }
- if (optionsOrFn.fn != undefined) {
- throw new TypeError(
- "Unexpected 'fn' field in options, bench function is already provided as the third argument.",
- );
- }
- if (optionsOrFn.name != undefined) {
- throw new TypeError(
- "Unexpected 'name' field in options, bench name is already provided as the first argument.",
- );
- }
- benchDesc = {
- ...defaults,
- ...optionsOrFn,
- fn: maybeFn,
- name: nameOrFnOrOptions,
- };
- }
- } else if (typeof nameOrFnOrOptions === "function") {
- if (!nameOrFnOrOptions.name) {
- throw new TypeError("The bench function must have a name");
+ let benchDesc;
+ const defaults = {
+ ignore: false,
+ baseline: false,
+ only: false,
+ sanitizeExit: true,
+ permissions: null,
+ };
+
+ if (typeof nameOrFnOrOptions === "string") {
+ if (!nameOrFnOrOptions) {
+ throw new TypeError("The bench name can't be empty");
+ }
+ if (typeof optionsOrFn === "function") {
+ benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
+ } else {
+ if (!maybeFn || typeof maybeFn !== "function") {
+ throw new TypeError("Missing bench function");
}
- if (optionsOrFn != undefined) {
- throw new TypeError("Unexpected second argument to Deno.bench()");
+ if (optionsOrFn.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, bench function is already provided as the third argument.",
+ );
}
- if (maybeFn != undefined) {
- throw new TypeError("Unexpected third argument to Deno.bench()");
+ if (optionsOrFn.name != undefined) {
+ throw new TypeError(
+ "Unexpected 'name' field in options, bench name is already provided as the first argument.",
+ );
}
benchDesc = {
...defaults,
- fn: nameOrFnOrOptions,
- name: nameOrFnOrOptions.name,
+ ...optionsOrFn,
+ fn: maybeFn,
+ name: nameOrFnOrOptions,
};
- } else {
- let fn;
- let name;
- if (typeof optionsOrFn === "function") {
- fn = optionsOrFn;
- if (nameOrFnOrOptions.fn != undefined) {
- throw new TypeError(
- "Unexpected 'fn' field in options, bench function is already provided as the second argument.",
- );
- }
- name = nameOrFnOrOptions.name ?? fn.name;
- } else {
- if (
- !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
- ) {
- throw new TypeError(
- "Expected 'fn' field in the first argument to be a bench function.",
- );
- }
- fn = nameOrFnOrOptions.fn;
- name = nameOrFnOrOptions.name ?? fn.name;
+ }
+ } else if (typeof nameOrFnOrOptions === "function") {
+ if (!nameOrFnOrOptions.name) {
+ throw new TypeError("The bench function must have a name");
+ }
+ if (optionsOrFn != undefined) {
+ throw new TypeError("Unexpected second argument to Deno.bench()");
+ }
+ if (maybeFn != undefined) {
+ throw new TypeError("Unexpected third argument to Deno.bench()");
+ }
+ benchDesc = {
+ ...defaults,
+ fn: nameOrFnOrOptions,
+ name: nameOrFnOrOptions.name,
+ };
+ } else {
+ let fn;
+ let name;
+ if (typeof optionsOrFn === "function") {
+ fn = optionsOrFn;
+ if (nameOrFnOrOptions.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, bench function is already provided as the second argument.",
+ );
}
- if (!name) {
- throw new TypeError("The bench name can't be empty");
+ name = nameOrFnOrOptions.name ?? fn.name;
+ } else {
+ if (
+ !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
+ ) {
+ throw new TypeError(
+ "Expected 'fn' field in the first argument to be a bench function.",
+ );
}
- benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
+ fn = nameOrFnOrOptions.fn;
+ name = nameOrFnOrOptions.name ?? fn.name;
}
-
- const AsyncFunction = (async () => {}).constructor;
- benchDesc.async = AsyncFunction === benchDesc.fn.constructor;
- benchDesc.fn = wrapBenchmark(benchDesc);
- benchDesc.warmup = false;
- benchDesc.name = escapeName(benchDesc.name);
-
- const { id, origin } = ops.op_register_bench(benchDesc);
- benchDesc.id = id;
- benchDesc.origin = origin;
- }
-
- function compareMeasurements(a, b) {
- if (a > b) return 1;
- if (a < b) return -1;
-
- return 0;
+ if (!name) {
+ throw new TypeError("The bench name can't be empty");
+ }
+ benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
}
- function benchStats(
+ const AsyncFunction = (async () => {}).constructor;
+ benchDesc.async = AsyncFunction === benchDesc.fn.constructor;
+ benchDesc.fn = wrapBenchmark(benchDesc);
+ benchDesc.warmup = false;
+ benchDesc.name = escapeName(benchDesc.name);
+
+ const { id, origin } = ops.op_register_bench(benchDesc);
+ benchDesc.id = id;
+ benchDesc.origin = origin;
+}
+
+function compareMeasurements(a, b) {
+ if (a > b) return 1;
+ if (a < b) return -1;
+
+ return 0;
+}
+
+function benchStats(
+ n,
+ highPrecision,
+ usedExplicitTimers,
+ avg,
+ min,
+ max,
+ all,
+) {
+ return {
n,
- highPrecision,
- usedExplicitTimers,
- avg,
min,
max,
- all,
- ) {
- return {
- n,
- min,
- max,
- p75: all[Math.ceil(n * (75 / 100)) - 1],
- p99: all[Math.ceil(n * (99 / 100)) - 1],
- p995: all[Math.ceil(n * (99.5 / 100)) - 1],
- p999: all[Math.ceil(n * (99.9 / 100)) - 1],
- avg: !highPrecision ? (avg / n) : Math.ceil(avg / n),
- highPrecision,
- usedExplicitTimers,
- };
+ p75: all[MathCeil(n * (75 / 100)) - 1],
+ p99: all[MathCeil(n * (99 / 100)) - 1],
+ p995: all[MathCeil(n * (99.5 / 100)) - 1],
+ p999: all[MathCeil(n * (99.9 / 100)) - 1],
+ avg: !highPrecision ? (avg / n) : MathCeil(avg / n),
+ highPrecision,
+ usedExplicitTimers,
+ };
+}
+
+async function benchMeasure(timeBudget, fn, async, context) {
+ let n = 0;
+ let avg = 0;
+ let wavg = 0;
+ let usedExplicitTimers = false;
+ const all = [];
+ let min = Infinity;
+ let max = -Infinity;
+ const lowPrecisionThresholdInNs = 1e4;
+
+ // warmup step
+ let c = 0;
+ let iterations = 20;
+ let budget = 10 * 1e6;
+
+ if (!async) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ fn(context);
+ const t2 = benchNow();
+ const totalTime = t2 - t1;
+ if (currentBenchUserExplicitStart !== null) {
+ currentBenchUserExplicitStart = null;
+ usedExplicitTimers = true;
+ }
+ if (currentBenchUserExplicitEnd !== null) {
+ currentBenchUserExplicitEnd = null;
+ usedExplicitTimers = true;
+ }
+
+ c++;
+ wavg += totalTime;
+ budget -= totalTime;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ await fn(context);
+ const t2 = benchNow();
+ const totalTime = t2 - t1;
+ if (currentBenchUserExplicitStart !== null) {
+ currentBenchUserExplicitStart = null;
+ usedExplicitTimers = true;
+ }
+ if (currentBenchUserExplicitEnd !== null) {
+ currentBenchUserExplicitEnd = null;
+ usedExplicitTimers = true;
+ }
+
+ c++;
+ wavg += totalTime;
+ budget -= totalTime;
+ }
}
- async function benchMeasure(timeBudget, fn, async, context) {
- let n = 0;
- let avg = 0;
- let wavg = 0;
- let usedExplicitTimers = false;
- const all = [];
- let min = Infinity;
- let max = -Infinity;
- const lowPrecisionThresholdInNs = 1e4;
-
- // warmup step
- let c = 0;
- let iterations = 20;
- let budget = 10 * 1e6;
+ wavg /= c;
+
+ // measure step
+ if (wavg > lowPrecisionThresholdInNs) {
+ let iterations = 10;
+ let budget = timeBudget * 1e6;
if (!async) {
while (budget > 0 || iterations-- > 0) {
@@ -976,18 +1041,22 @@
fn(context);
const t2 = benchNow();
const totalTime = t2 - t1;
+ let measuredTime = totalTime;
if (currentBenchUserExplicitStart !== null) {
+ measuredTime -= currentBenchUserExplicitStart - t1;
currentBenchUserExplicitStart = null;
- usedExplicitTimers = true;
}
if (currentBenchUserExplicitEnd !== null) {
+ measuredTime -= t2 - currentBenchUserExplicitEnd;
currentBenchUserExplicitEnd = null;
- usedExplicitTimers = true;
}
- c++;
- wavg += totalTime;
+ n++;
+ avg += measuredTime;
budget -= totalTime;
+ ArrayPrototypePush(all, measuredTime);
+ if (measuredTime < min) min = measuredTime;
+ if (measuredTime > max) max = measuredTime;
}
} else {
while (budget > 0 || iterations-- > 0) {
@@ -995,382 +1064,329 @@
await fn(context);
const t2 = benchNow();
const totalTime = t2 - t1;
+ let measuredTime = totalTime;
if (currentBenchUserExplicitStart !== null) {
+ measuredTime -= currentBenchUserExplicitStart - t1;
currentBenchUserExplicitStart = null;
- usedExplicitTimers = true;
}
if (currentBenchUserExplicitEnd !== null) {
+ measuredTime -= t2 - currentBenchUserExplicitEnd;
currentBenchUserExplicitEnd = null;
- usedExplicitTimers = true;
}
- c++;
- wavg += totalTime;
+ n++;
+ avg += measuredTime;
budget -= totalTime;
+ ArrayPrototypePush(all, measuredTime);
+ if (measuredTime < min) min = measuredTime;
+ if (measuredTime > max) max = measuredTime;
}
}
+ } else {
+ context.start = function start() {};
+ context.end = function end() {};
+ let iterations = 10;
+ let budget = timeBudget * 1e6;
- wavg /= c;
-
- // measure step
- if (wavg > lowPrecisionThresholdInNs) {
- let iterations = 10;
- let budget = timeBudget * 1e6;
-
- if (!async) {
- while (budget > 0 || iterations-- > 0) {
- const t1 = benchNow();
+ if (!async) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ for (let c = 0; c < lowPrecisionThresholdInNs; c++) {
fn(context);
- const t2 = benchNow();
- const totalTime = t2 - t1;
- let measuredTime = totalTime;
- if (currentBenchUserExplicitStart !== null) {
- measuredTime -= currentBenchUserExplicitStart - t1;
- currentBenchUserExplicitStart = null;
- }
- if (currentBenchUserExplicitEnd !== null) {
- measuredTime -= t2 - currentBenchUserExplicitEnd;
- currentBenchUserExplicitEnd = null;
- }
-
- n++;
- avg += measuredTime;
- budget -= totalTime;
- all.push(measuredTime);
- if (measuredTime < min) min = measuredTime;
- if (measuredTime > max) max = measuredTime;
- }
- } else {
- while (budget > 0 || iterations-- > 0) {
- const t1 = benchNow();
- await fn(context);
- const t2 = benchNow();
- const totalTime = t2 - t1;
- let measuredTime = totalTime;
- if (currentBenchUserExplicitStart !== null) {
- measuredTime -= currentBenchUserExplicitStart - t1;
- currentBenchUserExplicitStart = null;
- }
- if (currentBenchUserExplicitEnd !== null) {
- measuredTime -= t2 - currentBenchUserExplicitEnd;
- currentBenchUserExplicitEnd = null;
- }
-
- n++;
- avg += measuredTime;
- budget -= totalTime;
- all.push(measuredTime);
- if (measuredTime < min) min = measuredTime;
- if (measuredTime > max) max = measuredTime;
}
+ const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
+
+ n++;
+ avg += iterationTime;
+ ArrayPrototypePush(all, iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ budget -= iterationTime * lowPrecisionThresholdInNs;
}
} else {
- context.start = function start() {};
- context.end = function end() {};
- let iterations = 10;
- let budget = timeBudget * 1e6;
-
- if (!async) {
- while (budget > 0 || iterations-- > 0) {
- const t1 = benchNow();
- for (let c = 0; c < lowPrecisionThresholdInNs; c++) {
- fn(context);
- }
- const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
-
- n++;
- avg += iterationTime;
- all.push(iterationTime);
- if (iterationTime < min) min = iterationTime;
- if (iterationTime > max) max = iterationTime;
- budget -= iterationTime * lowPrecisionThresholdInNs;
- }
- } else {
- while (budget > 0 || iterations-- > 0) {
- const t1 = benchNow();
- for (let c = 0; c < lowPrecisionThresholdInNs; c++) {
- await fn(context);
- currentBenchUserExplicitStart = null;
- currentBenchUserExplicitEnd = null;
- }
- const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
-
- n++;
- avg += iterationTime;
- all.push(iterationTime);
- if (iterationTime < min) min = iterationTime;
- if (iterationTime > max) max = iterationTime;
- budget -= iterationTime * lowPrecisionThresholdInNs;
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ for (let c = 0; c < lowPrecisionThresholdInNs; c++) {
+ await fn(context);
+ currentBenchUserExplicitStart = null;
+ currentBenchUserExplicitEnd = null;
}
+ const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
+
+ n++;
+ avg += iterationTime;
+ ArrayPrototypePush(all, iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ budget -= iterationTime * lowPrecisionThresholdInNs;
}
}
-
- all.sort(compareMeasurements);
- return benchStats(
- n,
- wavg > lowPrecisionThresholdInNs,
- usedExplicitTimers,
- avg,
- min,
- max,
- all,
- );
}
- /** @param desc {BenchDescription} */
- function createBenchContext(desc) {
- return {
- [Symbol.toStringTag]: "BenchContext",
- name: desc.name,
- origin: desc.origin,
- start() {
- if (currentBenchId !== desc.id) {
- throw new TypeError(
- "The benchmark which this context belongs to is not being executed.",
- );
- }
- if (currentBenchUserExplicitStart != null) {
- throw new TypeError(
- "BenchContext::start() has already been invoked.",
- );
- }
- currentBenchUserExplicitStart = benchNow();
- },
- end() {
- const end = benchNow();
- if (currentBenchId !== desc.id) {
- throw new TypeError(
- "The benchmark which this context belongs to is not being executed.",
- );
- }
- if (currentBenchUserExplicitEnd != null) {
- throw new TypeError("BenchContext::end() has already been invoked.");
- }
- currentBenchUserExplicitEnd = end;
- },
- };
- }
+ all.sort(compareMeasurements);
+ return benchStats(
+ n,
+ wavg > lowPrecisionThresholdInNs,
+ usedExplicitTimers,
+ avg,
+ min,
+ max,
+ all,
+ );
+}
+
+/** @param desc {BenchDescription} */
+function createBenchContext(desc) {
+ return {
+ [SymbolToStringTag]: "BenchContext",
+ name: desc.name,
+ origin: desc.origin,
+ start() {
+ if (currentBenchId !== desc.id) {
+ throw new TypeError(
+ "The benchmark which this context belongs to is not being executed.",
+ );
+ }
+ if (currentBenchUserExplicitStart != null) {
+ throw new TypeError(
+ "BenchContext::start() has already been invoked.",
+ );
+ }
+ currentBenchUserExplicitStart = benchNow();
+ },
+ end() {
+ const end = benchNow();
+ if (currentBenchId !== desc.id) {
+ throw new TypeError(
+ "The benchmark which this context belongs to is not being executed.",
+ );
+ }
+ if (currentBenchUserExplicitEnd != null) {
+ throw new TypeError("BenchContext::end() has already been invoked.");
+ }
+ currentBenchUserExplicitEnd = end;
+ },
+ };
+}
+
+/** Wrap a user benchmark function in one which returns a structured result. */
+function wrapBenchmark(desc) {
+ const fn = desc.fn;
+ return async function outerWrapped() {
+ let token = null;
+ const originalConsole = globalThis.console;
+ currentBenchId = desc.id;
+
+ try {
+ globalThis.console = new Console((s) => {
+ ops.op_dispatch_bench_event({ output: s });
+ });
+
+ if (desc.permissions) {
+ token = pledgePermissions(desc.permissions);
+ }
- /** Wrap a user benchmark function in one which returns a structured result. */
- function wrapBenchmark(desc) {
- const fn = desc.fn;
- return async function outerWrapped() {
- let token = null;
- const originalConsole = globalThis.console;
- currentBenchId = desc.id;
-
- try {
- globalThis.console = new Console((s) => {
- ops.op_dispatch_bench_event({ output: s });
+ if (desc.sanitizeExit) {
+ setExitHandler((exitCode) => {
+ throw new Error(
+ `Bench attempted to exit with exit code: ${exitCode}`,
+ );
});
+ }
- if (desc.permissions) {
- token = pledgePermissions(desc.permissions);
- }
+ const benchTimeInMs = 500;
+ const context = createBenchContext(desc);
+ const stats = await benchMeasure(
+ benchTimeInMs,
+ fn,
+ desc.async,
+ context,
+ );
- if (desc.sanitizeExit) {
- setExitHandler((exitCode) => {
- throw new Error(
- `Bench attempted to exit with exit code: ${exitCode}`,
- );
- });
- }
+ return { ok: stats };
+ } catch (error) {
+ return { failed: core.destructureError(error) };
+ } finally {
+ globalThis.console = originalConsole;
+ currentBenchId = null;
+ currentBenchUserExplicitStart = null;
+ currentBenchUserExplicitEnd = null;
+ if (bench.sanitizeExit) setExitHandler(null);
+ if (token !== null) restorePermissions(token);
+ }
+ };
+}
- const benchTimeInMs = 500;
- const context = createBenchContext(desc);
- const stats = await benchMeasure(
- benchTimeInMs,
- fn,
- desc.async,
- context,
- );
+function benchNow() {
+ return ops.op_bench_now();
+}
- return { ok: stats };
- } catch (error) {
- return { failed: core.destructureError(error) };
- } finally {
- globalThis.console = originalConsole;
- currentBenchId = null;
- currentBenchUserExplicitStart = null;
- currentBenchUserExplicitEnd = null;
- if (bench.sanitizeExit) setExitHandler(null);
- if (token !== null) restorePermissions(token);
- }
- };
+function getFullName(desc) {
+ if ("parent" in desc) {
+ return `${getFullName(desc.parent)} ... ${desc.name}`;
}
+ return desc.name;
+}
- function benchNow() {
- return ops.op_bench_now();
- }
+function usesSanitizer(desc) {
+ return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit;
+}
- function getFullName(desc) {
- if ("parent" in desc) {
- return `${getFullName(desc.parent)} ... ${desc.name}`;
- }
- return desc.name;
+function stepReportResult(desc, result, elapsed) {
+ const state = MapPrototypeGet(testStates, desc.id);
+ for (const childDesc of state.children) {
+ stepReportResult(childDesc, { failed: "incomplete" }, 0);
}
-
- function usesSanitizer(desc) {
- return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit;
+ if (result === "ok") {
+ ops.op_test_event_step_result_ok(desc.id, elapsed);
+ } else if (result === "ignored") {
+ ops.op_test_event_step_result_ignored(desc.id, elapsed);
+ } else {
+ ops.op_test_event_step_result_failed(desc.id, result.failed, elapsed);
}
-
- function stepReportResult(desc, result, elapsed) {
- const state = testStates.get(desc.id);
- for (const childDesc of state.children) {
- stepReportResult(childDesc, { failed: "incomplete" }, 0);
- }
- if (result === "ok") {
- ops.op_test_event_step_result_ok(desc.id, elapsed);
- } else if (result === "ignored") {
- ops.op_test_event_step_result_ignored(desc.id, elapsed);
- } else {
- ops.op_test_event_step_result_failed(desc.id, result.failed, elapsed);
- }
+}
+
+/** @param desc {TestDescription | TestStepDescription} */
+function createTestContext(desc) {
+ let parent;
+ let level;
+ let rootId;
+ let rootName;
+ if ("parent" in desc) {
+ parent = MapPrototypeGet(testStates, desc.parent.id).context;
+ level = desc.level;
+ rootId = desc.rootId;
+ rootName = desc.rootName;
+ } else {
+ parent = undefined;
+ level = 0;
+ rootId = desc.id;
+ rootName = desc.name;
}
+ return {
+ [SymbolToStringTag]: "TestContext",
+ /**
+ * The current test name.
+ */
+ name: desc.name,
+ /**
+ * Parent test context.
+ */
+ parent,
+ /**
+ * File Uri of the test code.
+ */
+ origin: desc.origin,
+ /**
+ * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise<void>)}
+ * @param maybeFn {((t: TestContext) => void | Promise<void>) | undefined}
+ */
+ async step(nameOrFnOrOptions, maybeFn) {
+ if (MapPrototypeGet(testStates, desc.id).completed) {
+ throw new Error(
+ "Cannot run test step after parent scope has finished execution. " +
+ "Ensure any `.step(...)` calls are executed before their parent scope completes execution.",
+ );
+ }
- /** @param desc {TestDescription | TestStepDescription} */
- function createTestContext(desc) {
- let parent;
- let level;
- let rootId;
- let rootName;
- if ("parent" in desc) {
- parent = testStates.get(desc.parent.id).context;
- level = desc.level;
- rootId = desc.rootId;
- rootName = desc.rootName;
- } else {
- parent = undefined;
- level = 0;
- rootId = desc.id;
- rootName = desc.name;
- }
- return {
- [Symbol.toStringTag]: "TestContext",
- /**
- * The current test name.
- */
- name: desc.name,
- /**
- * Parent test context.
- */
- parent,
- /**
- * File Uri of the test code.
- */
- origin: desc.origin,
- /**
- * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise<void>)}
- * @param maybeFn {((t: TestContext) => void | Promise<void>) | undefined}
- */
- async step(nameOrFnOrOptions, maybeFn) {
- if (testStates.get(desc.id).completed) {
- throw new Error(
- "Cannot run test step after parent scope has finished execution. " +
- "Ensure any `.step(...)` calls are executed before their parent scope completes execution.",
- );
+ let stepDesc;
+ if (typeof nameOrFnOrOptions === "string") {
+ if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, maybeFn))) {
+ throw new TypeError("Expected function for second argument.");
}
-
- let stepDesc;
- if (typeof nameOrFnOrOptions === "string") {
- if (
- !Object.prototype.isPrototypeOf.call(Function.prototype, maybeFn)
- ) {
- throw new TypeError("Expected function for second argument.");
- }
- stepDesc = {
- name: nameOrFnOrOptions,
- fn: maybeFn,
- };
- } else if (typeof nameOrFnOrOptions === "function") {
- if (!nameOrFnOrOptions.name) {
- throw new TypeError("The step function must have a name.");
- }
- if (maybeFn != undefined) {
- throw new TypeError(
- "Unexpected second argument to TestContext.step()",
- );
- }
- stepDesc = {
- name: nameOrFnOrOptions.name,
- fn: nameOrFnOrOptions,
- };
- } else if (typeof nameOrFnOrOptions === "object") {
- stepDesc = nameOrFnOrOptions;
- } else {
+ stepDesc = {
+ name: nameOrFnOrOptions,
+ fn: maybeFn,
+ };
+ } else if (typeof nameOrFnOrOptions === "function") {
+ if (!nameOrFnOrOptions.name) {
+ throw new TypeError("The step function must have a name.");
+ }
+ if (maybeFn != undefined) {
throw new TypeError(
- "Expected a test definition or name and function.",
+ "Unexpected second argument to TestContext.step()",
);
}
- stepDesc.ignore ??= false;
- stepDesc.sanitizeOps ??= desc.sanitizeOps;
- stepDesc.sanitizeResources ??= desc.sanitizeResources;
- stepDesc.sanitizeExit ??= desc.sanitizeExit;
- stepDesc.location = core.currentUserCallSite();
- stepDesc.level = level + 1;
- stepDesc.parent = desc;
- stepDesc.rootId = rootId;
- stepDesc.name = escapeName(stepDesc.name);
- stepDesc.rootName = escapeName(rootName);
- stepDesc.fn = wrapTest(stepDesc);
- const id = ops.op_register_test_step(
- stepDesc.name,
- stepDesc.location.fileName,
- stepDesc.location.lineNumber,
- stepDesc.location.columnNumber,
- stepDesc.level,
- stepDesc.parent.id,
- stepDesc.rootId,
- stepDesc.rootName,
- );
- stepDesc.id = id;
- stepDesc.origin = desc.origin;
- const state = {
- context: createTestContext(stepDesc),
- children: [],
- failed: false,
- completed: false,
+ stepDesc = {
+ name: nameOrFnOrOptions.name,
+ fn: nameOrFnOrOptions,
};
- testStates.set(stepDesc.id, state);
- testStates.get(stepDesc.parent.id).children.push(
- stepDesc,
+ } else if (typeof nameOrFnOrOptions === "object") {
+ stepDesc = nameOrFnOrOptions;
+ } else {
+ throw new TypeError(
+ "Expected a test definition or name and function.",
);
+ }
+ stepDesc.ignore ??= false;
+ stepDesc.sanitizeOps ??= desc.sanitizeOps;
+ stepDesc.sanitizeResources ??= desc.sanitizeResources;
+ stepDesc.sanitizeExit ??= desc.sanitizeExit;
+ stepDesc.location = core.currentUserCallSite();
+ stepDesc.level = level + 1;
+ stepDesc.parent = desc;
+ stepDesc.rootId = rootId;
+ stepDesc.name = escapeName(stepDesc.name);
+ stepDesc.rootName = escapeName(rootName);
+ stepDesc.fn = wrapTest(stepDesc);
+ const id = ops.op_register_test_step(
+ stepDesc.name,
+ stepDesc.location.fileName,
+ stepDesc.location.lineNumber,
+ stepDesc.location.columnNumber,
+ stepDesc.level,
+ stepDesc.parent.id,
+ stepDesc.rootId,
+ stepDesc.rootName,
+ );
+ stepDesc.id = id;
+ stepDesc.origin = desc.origin;
+ const state = {
+ context: createTestContext(stepDesc),
+ children: [],
+ failed: false,
+ completed: false,
+ };
+ MapPrototypeSet(testStates, stepDesc.id, state);
+ ArrayPrototypePush(
+ MapPrototypeGet(testStates, stepDesc.parent.id).children,
+ stepDesc,
+ );
- ops.op_test_event_step_wait(stepDesc.id);
- const earlier = Date.now();
- const result = await stepDesc.fn(stepDesc);
- const elapsed = Date.now() - earlier;
- state.failed = !!result.failed;
- stepReportResult(stepDesc, result, elapsed);
- return result == "ok";
- },
- };
+ ops.op_test_event_step_wait(stepDesc.id);
+ const earlier = DateNow();
+ const result = await stepDesc.fn(stepDesc);
+ const elapsed = DateNow() - earlier;
+ state.failed = !!result.failed;
+ stepReportResult(stepDesc, result, elapsed);
+ return result == "ok";
+ },
+ };
+}
+
+/**
+ * Wrap a user test function in one which returns a structured result.
+ * @template T {Function}
+ * @param testFn {T}
+ * @param desc {TestDescription | TestStepDescription}
+ * @returns {T}
+ */
+function wrapTest(desc) {
+ let testFn = wrapInner(desc.fn);
+ if (desc.sanitizeOps) {
+ testFn = assertOps(testFn);
}
-
- /**
- * Wrap a user test function in one which returns a structured result.
- * @template T {Function}
- * @param testFn {T}
- * @param desc {TestDescription | TestStepDescription}
- * @returns {T}
- */
- function wrapTest(desc) {
- let testFn = wrapInner(desc.fn);
- if (desc.sanitizeOps) {
- testFn = assertOps(testFn);
- }
- if (desc.sanitizeResources) {
- testFn = assertResources(testFn);
- }
- if (desc.sanitizeExit) {
- testFn = assertExit(testFn, true);
- }
- if (!("parent" in desc) && desc.permissions) {
- testFn = withPermissions(testFn, desc.permissions);
- }
- return wrapOuter(testFn, desc);
+ if (desc.sanitizeResources) {
+ testFn = assertResources(testFn);
+ }
+ if (desc.sanitizeExit) {
+ testFn = assertExit(testFn, true);
+ }
+ if (!("parent" in desc) && desc.permissions) {
+ testFn = withPermissions(testFn, desc.permissions);
}
+ return wrapOuter(testFn, desc);
+}
- globalThis.Deno.bench = bench;
- globalThis.Deno.test = test;
-})();
+globalThis.Deno.bench = bench;
+globalThis.Deno.test = test;