summaryrefslogtreecommitdiff
path: root/cli/js/40_test.js
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js/40_test.js')
-rw-r--r--cli/js/40_test.js183
1 files changed, 181 insertions, 2 deletions
diff --git a/cli/js/40_test.js b/cli/js/40_test.js
index 146163fdd..b6f966f20 100644
--- a/cli/js/40_test.js
+++ b/cli/js/40_test.js
@@ -15,8 +15,10 @@ const {
MapPrototypeGet,
MapPrototypeHas,
MapPrototypeSet,
+ ObjectKeys,
Promise,
SafeArrayIterator,
+ Set,
SymbolToStringTag,
TypeError,
} = primordials;
@@ -340,6 +342,182 @@ function assertOps(fn) {
};
}
+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.";
+ }
+}
+
+// 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) {
@@ -542,8 +720,6 @@ function testInner(
testDesc.name,
testDesc.ignore,
testDesc.only,
- false, /*testDesc.sanitizeOps*/
- testDesc.sanitizeResources,
testDesc.location.fileName,
testDesc.location.lineNumber,
testDesc.location.columnNumber,
@@ -734,6 +910,9 @@ function wrapTest(desc) {
if (desc.sanitizeOps) {
testFn = assertOps(testFn);
}
+ if (desc.sanitizeResources) {
+ testFn = assertResources(testFn);
+ }
if (desc.sanitizeExit) {
testFn = assertExit(testFn, true);
}