summaryrefslogtreecommitdiff
path: root/tests/unit/net_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/net_test.ts')
-rw-r--r--tests/unit/net_test.ts1274
1 files changed, 1274 insertions, 0 deletions
diff --git a/tests/unit/net_test.ts b/tests/unit/net_test.ts
new file mode 100644
index 000000000..eae1ae533
--- /dev/null
+++ b/tests/unit/net_test.ts
@@ -0,0 +1,1274 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ assertNotEquals,
+ assertRejects,
+ assertThrows,
+ delay,
+ execCode,
+ execCode2,
+ tmpUnixSocketPath,
+} from "./test_util.ts";
+
+// Since these tests may run in parallel, ensure this port is unique to this file
+const listenPort = 4503;
+const listenPort2 = 4504;
+
+let isCI: boolean;
+try {
+ isCI = Deno.env.get("CI") !== undefined;
+} catch {
+ isCI = true;
+}
+
+Deno.test({ permissions: { net: true } }, function netTcpListenClose() {
+ const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort });
+ assert(listener.addr.transport === "tcp");
+ assertEquals(listener.addr.hostname, "127.0.0.1");
+ assertEquals(listener.addr.port, listenPort);
+ assertNotEquals(listener.rid, 0);
+ listener.close();
+});
+
+Deno.test(
+ {
+ permissions: { net: true },
+ },
+ function netUdpListenClose() {
+ const socket = Deno.listenDatagram({
+ hostname: "127.0.0.1",
+ port: listenPort,
+ transport: "udp",
+ });
+ assert(socket.addr.transport === "udp");
+ assertEquals(socket.addr.hostname, "127.0.0.1");
+ assertEquals(socket.addr.port, listenPort);
+ socket.close();
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ function netUnixListenClose() {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listen({
+ path: filePath,
+ transport: "unix",
+ });
+ assert(socket.addr.transport === "unix");
+ assertEquals(socket.addr.path, filePath);
+ socket.close();
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ function netUnixPacketListenClose() {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ assert(socket.addr.transport === "unixpacket");
+ assertEquals(socket.addr.path, filePath);
+ socket.close();
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: false },
+ },
+ function netUnixListenWritePermission() {
+ assertThrows(() => {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listen({
+ path: filePath,
+ transport: "unix",
+ });
+ assert(socket.addr.transport === "unix");
+ assertEquals(socket.addr.path, filePath);
+ socket.close();
+ }, Deno.errors.PermissionDenied);
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: false },
+ },
+ function netUnixPacketListenWritePermission() {
+ assertThrows(() => {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ assert(socket.addr.transport === "unixpacket");
+ assertEquals(socket.addr.path, filePath);
+ socket.close();
+ }, Deno.errors.PermissionDenied);
+ },
+);
+
+Deno.test(
+ {
+ permissions: { net: true },
+ },
+ async function netTcpCloseWhileAccept() {
+ const listener = Deno.listen({ port: listenPort });
+ const p = listener.accept();
+ listener.close();
+ // TODO(piscisaureus): the error type should be `Interrupted` here, which
+ // gets thrown, but then ext/net catches it and rethrows `BadResource`.
+ await assertRejects(
+ () => p,
+ Deno.errors.BadResource,
+ "Listener has been closed",
+ );
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function netUnixCloseWhileAccept() {
+ const filePath = tmpUnixSocketPath();
+ const listener = Deno.listen({
+ path: filePath,
+ transport: "unix",
+ });
+ const p = listener.accept();
+ listener.close();
+ await assertRejects(
+ () => p,
+ Deno.errors.BadResource,
+ "Listener has been closed",
+ );
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netTcpConcurrentAccept() {
+ const listener = Deno.listen({ port: 4510 });
+ let acceptErrCount = 0;
+ const checkErr = (e: Error) => {
+ if (e.message === "Listener has been closed") {
+ assertEquals(acceptErrCount, 1);
+ } else if (e.message === "Another accept task is ongoing") {
+ acceptErrCount++;
+ } else {
+ throw new Error("Unexpected error message");
+ }
+ };
+ const p = listener.accept().catch(checkErr);
+ const p1 = listener.accept().catch(checkErr);
+ await Promise.race([p, p1]);
+ listener.close();
+ await Promise.all([p, p1]);
+ assertEquals(acceptErrCount, 1);
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function netUnixConcurrentAccept() {
+ const filePath = tmpUnixSocketPath();
+ const listener = Deno.listen({ transport: "unix", path: filePath });
+ let acceptErrCount = 0;
+ const checkErr = (e: Error) => {
+ if (e.message === "Listener has been closed") {
+ assertEquals(acceptErrCount, 1);
+ } else if (e instanceof Deno.errors.Busy) { // "Listener already in use"
+ acceptErrCount++;
+ } else {
+ throw e;
+ }
+ };
+ const p = listener.accept().catch(checkErr);
+ const p1 = listener.accept().catch(checkErr);
+ await Promise.race([p, p1]);
+ listener.close();
+ await Promise.all([p, p1]);
+ assertEquals(acceptErrCount, 1);
+ },
+);
+
+Deno.test({ permissions: { net: true } }, async function netTcpDialListen() {
+ const listener = Deno.listen({ port: listenPort });
+ listener.accept().then(
+ async (conn) => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr.transport === "tcp");
+ assertEquals(conn.localAddr.hostname, "127.0.0.1");
+ assertEquals(conn.localAddr.port, listenPort);
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ },
+ );
+
+ const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
+ assert(conn.remoteAddr.transport === "tcp");
+ assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
+ assertEquals(conn.remoteAddr.port, listenPort);
+ assert(conn.localAddr != null);
+ const buf = new Uint8Array(1024);
+ const readResult = await conn.read(buf);
+ assertEquals(3, readResult);
+ assertEquals(1, buf[0]);
+ assertEquals(2, buf[1]);
+ assertEquals(3, buf[2]);
+ assert(conn.rid > 0);
+
+ assert(readResult !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+});
+
+Deno.test({ permissions: { net: true } }, async function netTcpSetNoDelay() {
+ const listener = Deno.listen({ port: listenPort });
+ listener.accept().then(
+ async (conn) => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr.transport === "tcp");
+ assertEquals(conn.localAddr.hostname, "127.0.0.1");
+ assertEquals(conn.localAddr.port, listenPort);
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ },
+ );
+
+ const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
+ conn.setNoDelay(true);
+ assert(conn.remoteAddr.transport === "tcp");
+ assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
+ assertEquals(conn.remoteAddr.port, listenPort);
+ assert(conn.localAddr != null);
+ const buf = new Uint8Array(1024);
+ const readResult = await conn.read(buf);
+ assertEquals(3, readResult);
+ assertEquals(1, buf[0]);
+ assertEquals(2, buf[1]);
+ assertEquals(3, buf[2]);
+ assert(conn.rid > 0);
+
+ assert(readResult !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+});
+
+Deno.test({ permissions: { net: true } }, async function netTcpSetKeepAlive() {
+ const listener = Deno.listen({ port: listenPort });
+ listener.accept().then(
+ async (conn) => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr.transport === "tcp");
+ assertEquals(conn.localAddr.hostname, "127.0.0.1");
+ assertEquals(conn.localAddr.port, listenPort);
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ },
+ );
+
+ const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
+ conn.setKeepAlive(true);
+ assert(conn.remoteAddr.transport === "tcp");
+ assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
+ assertEquals(conn.remoteAddr.port, listenPort);
+ assert(conn.localAddr != null);
+ const buf = new Uint8Array(1024);
+ const readResult = await conn.read(buf);
+ assertEquals(3, readResult);
+ assertEquals(1, buf[0]);
+ assertEquals(2, buf[1]);
+ assertEquals(3, buf[2]);
+ assert(conn.rid > 0);
+
+ assert(readResult !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+});
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function netUnixDialListen() {
+ const filePath = tmpUnixSocketPath();
+ const listener = Deno.listen({ path: filePath, transport: "unix" });
+ listener.accept().then(
+ async (conn) => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr.transport === "unix");
+ assertEquals(conn.localAddr.path, filePath);
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ },
+ );
+ const conn = await Deno.connect({ path: filePath, transport: "unix" });
+ assert(conn.remoteAddr.transport === "unix");
+ assertEquals(conn.remoteAddr.path, filePath);
+ assert(conn.remoteAddr != null);
+ const buf = new Uint8Array(1024);
+ const readResult = await conn.read(buf);
+ assertEquals(3, readResult);
+ assertEquals(1, buf[0]);
+ assertEquals(2, buf[1]);
+ assertEquals(3, buf[2]);
+ assert(conn.rid > 0);
+
+ assert(readResult !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netUdpSendReceive() {
+ const alice = Deno.listenDatagram({ port: listenPort, transport: "udp" });
+ assert(alice.addr.transport === "udp");
+ assertEquals(alice.addr.port, listenPort);
+ assertEquals(alice.addr.hostname, "127.0.0.1");
+
+ const bob = Deno.listenDatagram({ port: listenPort2, transport: "udp" });
+ assert(bob.addr.transport === "udp");
+ assertEquals(bob.addr.port, listenPort2);
+ assertEquals(bob.addr.hostname, "127.0.0.1");
+
+ const sent = new Uint8Array([1, 2, 3]);
+ const byteLength = await alice.send(sent, bob.addr);
+
+ assertEquals(byteLength, 3);
+
+ const [recvd, remote] = await bob.receive();
+ assert(remote.transport === "udp");
+ assertEquals(remote.port, listenPort);
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+ alice.close();
+ bob.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true }, ignore: true },
+ async function netUdpSendReceiveBroadcast() {
+ // Must bind sender to an address that can send to the broadcast address on MacOS.
+ // Macos will give us error 49 when sending the broadcast packet if we omit hostname here.
+ const alice = Deno.listenDatagram({
+ port: listenPort,
+ transport: "udp",
+ hostname: "0.0.0.0",
+ });
+
+ const bob = Deno.listenDatagram({
+ port: listenPort,
+ transport: "udp",
+ hostname: "0.0.0.0",
+ });
+ assert(bob.addr.transport === "udp");
+ assertEquals(bob.addr.port, listenPort);
+ assertEquals(bob.addr.hostname, "0.0.0.0");
+
+ const broadcastAddr = { ...bob.addr, hostname: "255.255.255.255" };
+
+ const sent = new Uint8Array([1, 2, 3]);
+ const byteLength = await alice.send(sent, broadcastAddr);
+
+ assertEquals(byteLength, 3);
+ const [recvd, remote] = await bob.receive();
+ assert(remote.transport === "udp");
+ assertEquals(remote.port, listenPort);
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+ alice.close();
+ bob.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true }, ignore: true },
+ async function netUdpMulticastV4() {
+ const listener = Deno.listenDatagram({
+ hostname: "0.0.0.0",
+ port: 5353,
+ transport: "udp",
+ reuseAddress: true,
+ });
+
+ const membership = await listener.joinMulticastV4(
+ "224.0.0.251",
+ "127.0.0.1",
+ );
+
+ membership.setLoopback(true);
+ membership.setLoopback(false);
+ membership.setTTL(50);
+ membership.leave();
+ listener.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true }, ignore: true },
+ async function netUdpMulticastV6() {
+ const listener = Deno.listenDatagram({
+ hostname: "::",
+ port: 5353,
+ transport: "udp",
+ reuseAddress: true,
+ });
+
+ const membership = await listener.joinMulticastV6(
+ "ff02::fb",
+ 1,
+ );
+
+ membership.setLoopback(true);
+ membership.setLoopback(false);
+ membership.leave();
+ listener.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true }, ignore: true },
+ async function netUdpSendReceiveMulticastv4() {
+ const alice = Deno.listenDatagram({
+ hostname: "0.0.0.0",
+ port: 5353,
+ transport: "udp",
+ reuseAddress: true,
+ loopback: true,
+ });
+
+ const bob = Deno.listenDatagram({
+ hostname: "0.0.0.0",
+ port: 5353,
+ transport: "udp",
+ reuseAddress: true,
+ });
+
+ const aliceMembership = await alice.joinMulticastV4(
+ "224.0.0.1",
+ "0.0.0.0",
+ );
+
+ const bobMembership = await bob.joinMulticastV4("224.0.0.1", "0.0.0.0");
+
+ const sent = new Uint8Array([1, 2, 3]);
+
+ await alice.send(sent, {
+ hostname: "224.0.0.1",
+ port: 5353,
+ transport: "udp",
+ });
+
+ const [recvd, remote] = await bob.receive();
+
+ assert(remote.transport === "udp");
+ assertEquals(remote.port, 5353);
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+
+ aliceMembership.leave();
+ bobMembership.leave();
+
+ alice.close();
+ bob.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true }, ignore: true },
+ async function netUdpMulticastLoopbackOption() {
+ // Must bind sender to an address that can send to the broadcast address on MacOS.
+ // Macos will give us error 49 when sending the broadcast packet if we omit hostname here.
+ const listener = Deno.listenDatagram({
+ port: 5353,
+ transport: "udp",
+ hostname: "0.0.0.0",
+ loopback: true,
+ reuseAddress: true,
+ });
+
+ const membership = await listener.joinMulticastV4(
+ "224.0.0.1",
+ "0.0.0.0",
+ );
+
+ // await membership.setLoopback(true);
+
+ const sent = new Uint8Array([1, 2, 3]);
+ const byteLength = await listener.send(sent, {
+ hostname: "224.0.0.1",
+ port: 5353,
+ transport: "udp",
+ });
+
+ assertEquals(byteLength, 3);
+ const [recvd, remote] = await listener.receive();
+ assert(remote.transport === "udp");
+ assertEquals(remote.port, 5353);
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+ membership.leave();
+ listener.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netUdpConcurrentSendReceive() {
+ const socket = Deno.listenDatagram({ port: listenPort, transport: "udp" });
+ assert(socket.addr.transport === "udp");
+ assertEquals(socket.addr.port, listenPort);
+ assertEquals(socket.addr.hostname, "127.0.0.1");
+
+ const recvPromise = socket.receive();
+
+ const sendBuf = new Uint8Array([1, 2, 3]);
+ const sendLen = await socket.send(sendBuf, socket.addr);
+ assertEquals(sendLen, 3);
+
+ const [recvBuf, _recvAddr] = await recvPromise;
+ assertEquals(recvBuf.length, 3);
+ assertEquals(1, recvBuf[0]);
+ assertEquals(2, recvBuf[1]);
+ assertEquals(3, recvBuf[2]);
+
+ socket.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netUdpBorrowMutError() {
+ const socket = Deno.listenDatagram({
+ port: listenPort,
+ transport: "udp",
+ });
+ // Panic happened on second send: BorrowMutError
+ const a = socket.send(new Uint8Array(), socket.addr);
+ const b = socket.send(new Uint8Array(), socket.addr);
+ await Promise.all([a, b]);
+ socket.close();
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function netUnixPacketSendReceive() {
+ const aliceFilePath = tmpUnixSocketPath();
+ const alice = Deno.listenDatagram({
+ path: aliceFilePath,
+ transport: "unixpacket",
+ });
+ assert(alice.addr.transport === "unixpacket");
+ assertEquals(alice.addr.path, aliceFilePath);
+
+ const bobFilePath = tmpUnixSocketPath();
+ const bob = Deno.listenDatagram({
+ path: bobFilePath,
+ transport: "unixpacket",
+ });
+ assert(bob.addr.transport === "unixpacket");
+ assertEquals(bob.addr.path, bobFilePath);
+
+ const sent = new Uint8Array([1, 2, 3]);
+ const byteLength = await alice.send(sent, bob.addr);
+ assertEquals(byteLength, 3);
+
+ const [recvd, remote] = await bob.receive();
+ assert(remote.transport === "unixpacket");
+ assertEquals(remote.path, aliceFilePath);
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+ alice.close();
+ bob.close();
+ },
+);
+
+// TODO(lucacasonato): support concurrent reads and writes on unixpacket sockets
+Deno.test(
+ { ignore: true, permissions: { read: true, write: true } },
+ async function netUnixPacketConcurrentSendReceive() {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ assert(socket.addr.transport === "unixpacket");
+ assertEquals(socket.addr.path, filePath);
+
+ const recvPromise = socket.receive();
+
+ const sendBuf = new Uint8Array([1, 2, 3]);
+ const sendLen = await socket.send(sendBuf, socket.addr);
+ assertEquals(sendLen, 3);
+
+ const [recvBuf, _recvAddr] = await recvPromise;
+ assertEquals(recvBuf.length, 3);
+ assertEquals(1, recvBuf[0]);
+ assertEquals(2, recvBuf[1]);
+ assertEquals(3, recvBuf[2]);
+
+ socket.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netTcpListenIteratorBreakClosesResource() {
+ async function iterate(listener: Deno.Listener) {
+ let i = 0;
+
+ for await (const conn of listener) {
+ conn.close();
+ i++;
+
+ if (i > 1) {
+ break;
+ }
+ }
+ }
+
+ const addr = { hostname: "127.0.0.1", port: 8888 };
+ const listener = Deno.listen(addr);
+ const iteratePromise = iterate(listener);
+
+ await delay(100);
+ const conn1 = await Deno.connect(addr);
+ conn1.close();
+ const conn2 = await Deno.connect(addr);
+ conn2.close();
+
+ await iteratePromise;
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netTcpListenCloseWhileIterating() {
+ const listener = Deno.listen({ port: 8001 });
+ const nextWhileClosing = listener[Symbol.asyncIterator]().next();
+ listener.close();
+ assertEquals(await nextWhileClosing, { value: undefined, done: true });
+
+ const nextAfterClosing = listener[Symbol.asyncIterator]().next();
+ assertEquals(await nextAfterClosing, { value: undefined, done: true });
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netUdpListenCloseWhileIterating() {
+ const socket = Deno.listenDatagram({ port: 8000, transport: "udp" });
+ const nextWhileClosing = socket[Symbol.asyncIterator]().next();
+ socket.close();
+ assertEquals(await nextWhileClosing, { value: undefined, done: true });
+
+ const nextAfterClosing = socket[Symbol.asyncIterator]().next();
+ assertEquals(await nextAfterClosing, { value: undefined, done: true });
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function netUnixListenCloseWhileIterating() {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listen({ path: filePath, transport: "unix" });
+ const nextWhileClosing = socket[Symbol.asyncIterator]().next();
+ socket.close();
+ assertEquals(await nextWhileClosing, { value: undefined, done: true });
+
+ const nextAfterClosing = socket[Symbol.asyncIterator]().next();
+ assertEquals(await nextAfterClosing, { value: undefined, done: true });
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function netUnixPacketListenCloseWhileIterating() {
+ const filePath = tmpUnixSocketPath();
+ const socket = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ const nextWhileClosing = socket[Symbol.asyncIterator]().next();
+ socket.close();
+ assertEquals(await nextWhileClosing, { value: undefined, done: true });
+
+ const nextAfterClosing = socket[Symbol.asyncIterator]().next();
+ assertEquals(await nextAfterClosing, { value: undefined, done: true });
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netListenAsyncIterator() {
+ const addr = { hostname: "127.0.0.1", port: listenPort };
+ const listener = Deno.listen(addr);
+ const runAsyncIterator = async () => {
+ for await (const conn of listener) {
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ }
+ };
+ runAsyncIterator();
+ const conn = await Deno.connect(addr);
+ const buf = new Uint8Array(1024);
+ const readResult = await conn.read(buf);
+ assertEquals(3, readResult);
+ assertEquals(1, buf[0]);
+ assertEquals(2, buf[1]);
+ assertEquals(3, buf[2]);
+ assert(conn.rid > 0);
+
+ assert(readResult !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+ },
+);
+
+Deno.test(
+ {
+ permissions: { net: true },
+ },
+ async function netCloseWriteSuccess() {
+ const addr = { hostname: "127.0.0.1", port: listenPort };
+ const listener = Deno.listen(addr);
+ const { promise: closePromise, resolve } = Promise.withResolvers<void>();
+ listener.accept().then(async (conn) => {
+ await conn.write(new Uint8Array([1, 2, 3]));
+ await closePromise;
+ conn.close();
+ });
+ const conn = await Deno.connect(addr);
+ conn.closeWrite(); // closing write
+ const buf = new Uint8Array(1024);
+ // Check read not impacted
+ const readResult = await conn.read(buf);
+ assertEquals(3, readResult);
+ assertEquals(1, buf[0]);
+ assertEquals(2, buf[1]);
+ assertEquals(3, buf[2]);
+ // Verify that the write end of the socket is closed.
+ // TODO(piscisaureus): assert that thrown error is of a specific type.
+ await assertRejects(async () => {
+ await conn.write(new Uint8Array([1, 2, 3]));
+ });
+ resolve();
+ listener.close();
+ conn.close();
+ },
+);
+
+Deno.test(
+ {
+ // https://github.com/denoland/deno/issues/11580
+ ignore: Deno.build.os === "darwin" && isCI,
+ permissions: { net: true },
+ },
+ async function netHangsOnClose() {
+ let acceptedConn: Deno.Conn;
+
+ async function iteratorReq(listener: Deno.Listener) {
+ const p = new Uint8Array(10);
+ const conn = await listener.accept();
+ acceptedConn = conn;
+
+ try {
+ while (true) {
+ const nread = await conn.read(p);
+ if (nread === null) {
+ break;
+ }
+ await conn.write(new Uint8Array([1, 2, 3]));
+ }
+ } catch (err) {
+ assert(err);
+ assert(err instanceof Deno.errors.Interrupted);
+ }
+ }
+
+ const addr = { hostname: "127.0.0.1", port: listenPort };
+ const listener = Deno.listen(addr);
+ const listenerPromise = iteratorReq(listener);
+ const connectionPromise = (async () => {
+ const conn = await Deno.connect(addr);
+ await conn.write(new Uint8Array([1, 2, 3, 4]));
+ const buf = new Uint8Array(10);
+ await conn.read(buf);
+ conn!.close();
+ acceptedConn!.close();
+ listener.close();
+ })();
+
+ await Promise.all([
+ listenerPromise,
+ connectionPromise,
+ ]);
+ },
+);
+
+Deno.test(
+ {
+ permissions: { net: true },
+ },
+ function netExplicitUndefinedHostname() {
+ const listener = Deno.listen({ hostname: undefined, port: 8080 });
+ assertEquals((listener.addr as Deno.NetAddr).hostname, "0.0.0.0");
+ listener.close();
+ },
+);
+
+Deno.test(
+ {
+ ignore: Deno.build.os !== "linux",
+ permissions: { read: true, write: true },
+ },
+ function netUnixAbstractPathShouldNotPanic() {
+ const listener = Deno.listen({
+ path: "\0aaa",
+ transport: "unix",
+ });
+ assert("not panic");
+ listener.close();
+ },
+);
+
+Deno.test({ permissions: { net: true } }, async function whatwgStreams() {
+ const server = (async () => {
+ const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort });
+ const conn = await listener.accept();
+ await conn.readable.pipeTo(conn.writable);
+ listener.close();
+ })();
+
+ const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
+ const reader = conn.readable.getReader();
+ const writer = conn.writable.getWriter();
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+ const data = encoder.encode("Hello World");
+
+ await writer.write(data);
+ const { value, done } = await reader.read();
+ assert(!done);
+ assertEquals(decoder.decode(value), "Hello World");
+ await reader.cancel();
+ await server;
+});
+
+Deno.test(
+ { permissions: { read: true } },
+ async function readableStreamTextEncoderPipe() {
+ const filename = "tests/testdata/assets/hello.txt";
+ const file = await Deno.open(filename);
+ const readable = file.readable.pipeThrough(new TextDecoderStream());
+ const chunks = [];
+ for await (const chunk of readable) {
+ chunks.push(chunk);
+ }
+ assertEquals(chunks.length, 1);
+ assertEquals(chunks[0].length, 12);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function writableStream() {
+ const path = await Deno.makeTempFile();
+ const file = await Deno.open(path, { write: true });
+ assert(file.writable instanceof WritableStream);
+ const readable = new ReadableStream({
+ start(controller) {
+ controller.enqueue(new TextEncoder().encode("hello "));
+ controller.enqueue(new TextEncoder().encode("world!"));
+ controller.close();
+ },
+ });
+ await readable.pipeTo(file.writable);
+ const res = await Deno.readTextFile(path);
+ assertEquals(res, "hello world!");
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, run: true } },
+ async function netListenUnref() {
+ const [statusCode, _output] = await execCode(`
+ async function main() {
+ const listener = Deno.listen({ port: ${listenPort} });
+ listener.unref();
+ await listener.accept(); // This doesn't block the program from exiting
+ }
+ main();
+ `);
+ assertEquals(statusCode, 0);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, run: true } },
+ async function netListenUnref2() {
+ const [statusCode, _output] = await execCode(`
+ async function main() {
+ const listener = Deno.listen({ port: ${listenPort} });
+ await listener.accept();
+ listener.unref();
+ await listener.accept(); // The program exits here
+ throw new Error(); // The program doesn't reach here
+ }
+ main();
+ const conn = await Deno.connect({ port: ${listenPort} });
+ conn.close();
+ `);
+ assertEquals(statusCode, 0);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, run: true, net: true } },
+ async function netListenUnrefAndRef() {
+ const p = execCode2(`
+ async function main() {
+ const listener = Deno.listen({ port: ${listenPort} });
+ listener.unref();
+ listener.ref(); // This restores 'ref' state of listener
+ console.log("started");
+ await listener.accept();
+ console.log("accepted")
+ }
+ main();
+ `);
+ await p.waitStdoutText("started");
+ const conn = await Deno.connect({ port: listenPort });
+ conn.close();
+ const [statusCode, output] = await p.finished();
+ assertEquals(statusCode, 0);
+ assertEquals(output.trim(), "started\naccepted");
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netListenUnrefConcurrentAccept() {
+ const timer = setTimeout(() => {}, 1000);
+ const listener = Deno.listen({ port: listenPort });
+ listener.accept().catch(() => {});
+ listener.unref();
+ // Unref'd listener still causes Busy error
+ // on concurrent accept calls.
+ await assertRejects(async () => {
+ await listener.accept(); // The program exits here
+ }, Deno.errors.Busy);
+ listener.close();
+ clearTimeout(timer);
+ },
+);
+
+Deno.test({
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+}, function netUnixListenAddrAlreadyInUse() {
+ const filePath = tmpUnixSocketPath();
+ const listener = Deno.listen({ path: filePath, transport: "unix" });
+ assertThrows(
+ () => {
+ Deno.listen({ path: filePath, transport: "unix" });
+ },
+ Deno.errors.AddrInUse,
+ );
+ listener.close();
+});
+
+Deno.test(
+ { permissions: { net: true, read: true, run: true } },
+ async function netConnUnref() {
+ const listener = Deno.listen({ port: listenPort });
+ const intervalId = setInterval(() => {}); // This keeps event loop alive.
+
+ const program = execCode(`
+ async function main() {
+ const conn = await Deno.connect({ port: ${listenPort} });
+ conn.unref();
+ await conn.read(new Uint8Array(10)); // The program exits here
+ throw new Error(); // The program doesn't reach here
+ }
+ main();
+ `);
+ const conn = await listener.accept();
+ const [statusCode, _output] = await program;
+ conn.close();
+ listener.close();
+ clearInterval(intervalId);
+ assertEquals(statusCode, 0);
+ },
+);
+
+Deno.test(
+ { permissions: { net: true, read: true, run: true } },
+ async function netConnUnrefReadable() {
+ const listener = Deno.listen({ port: listenPort });
+ const intervalId = setInterval(() => {}); // This keeps event loop alive.
+
+ const program = execCode(`
+ async function main() {
+ const conn = await Deno.connect({ port: ${listenPort} });
+ conn.unref();
+ const reader = conn.readable.getReader();
+ await reader.read(); // The program exits here
+ throw new Error(); // The program doesn't reach here
+ }
+ main();
+ `);
+ const conn = await listener.accept();
+ const [statusCode, _output] = await program;
+ conn.close();
+ listener.close();
+ clearInterval(intervalId);
+ assertEquals(statusCode, 0);
+ },
+);
+
+Deno.test({ permissions: { net: true } }, async function netTcpReuseAddr() {
+ const listener1 = Deno.listen({
+ hostname: "127.0.0.1",
+ port: listenPort,
+ });
+ listener1.accept().then(
+ (conn) => {
+ conn.close();
+ },
+ );
+
+ const conn1 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
+ const buf1 = new Uint8Array(1024);
+ await conn1.read(buf1);
+ listener1.close();
+ conn1.close();
+
+ const listener2 = Deno.listen({
+ hostname: "127.0.0.1",
+ port: listenPort,
+ });
+
+ listener2.accept().then(
+ (conn) => {
+ conn.close();
+ },
+ );
+
+ const conn2 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
+ const buf2 = new Uint8Array(1024);
+ await conn2.read(buf2);
+
+ listener2.close();
+ conn2.close();
+});
+
+Deno.test(
+ { permissions: { net: true } },
+ async function netUdpReuseAddr() {
+ const sender = Deno.listenDatagram({
+ port: 4002,
+ transport: "udp",
+ });
+ const listener1 = Deno.listenDatagram({
+ port: 4000,
+ transport: "udp",
+ reuseAddress: true,
+ });
+ const listener2 = Deno.listenDatagram({
+ port: 4000,
+ transport: "udp",
+ reuseAddress: true,
+ });
+
+ const sent = new Uint8Array([1, 2, 3]);
+ await sender.send(sent, listener1.addr);
+ await Promise.any([listener1.receive(), listener2.receive()]).then(
+ ([recvd, remote]) => {
+ assert(remote.transport === "udp");
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+ },
+ );
+ sender.close();
+ listener1.close();
+ listener2.close();
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ function netUdpNoReuseAddr() {
+ let listener1;
+ try {
+ listener1 = Deno.listenDatagram({
+ port: 4001,
+ transport: "udp",
+ reuseAddress: false,
+ });
+ } catch (err) {
+ assert(err);
+ assert(err instanceof Deno.errors.AddrInUse); // AddrInUse from previous test
+ }
+
+ assertThrows(() => {
+ Deno.listenDatagram({
+ port: 4001,
+ transport: "udp",
+ reuseAddress: false,
+ });
+ }, Deno.errors.AddrInUse);
+ if (typeof listener1 !== "undefined") {
+ listener1.close();
+ }
+ },
+);
+
+Deno.test({
+ ignore: Deno.build.os !== "linux",
+ permissions: { net: true },
+}, async function netTcpListenReusePort() {
+ const port = 4003;
+ const listener1 = Deno.listen({ port, reusePort: true });
+ const listener2 = Deno.listen({ port, reusePort: true });
+ let p1;
+ let p2;
+ let listener1Recv = false;
+ let listener2Recv = false;
+ while (!listener1Recv || !listener2Recv) {
+ if (!p1) {
+ p1 = listener1.accept().then((conn) => {
+ conn.close();
+ listener1Recv = true;
+ p1 = undefined;
+ }).catch(() => {});
+ }
+ if (!p2) {
+ p2 = listener2.accept().then((conn) => {
+ conn.close();
+ listener2Recv = true;
+ p2 = undefined;
+ }).catch(() => {});
+ }
+ const conn = await Deno.connect({ port });
+ conn.close();
+ await Promise.race([p1, p2]);
+ }
+ listener1.close();
+ listener2.close();
+});
+
+Deno.test({
+ ignore: Deno.build.os === "linux",
+ permissions: { net: true },
+}, function netTcpListenReusePortDoesNothing() {
+ const listener1 = Deno.listen({ port: 4003, reusePort: true });
+ assertThrows(() => {
+ Deno.listen({ port: 4003, reusePort: true });
+ }, Deno.errors.AddrInUse);
+ listener1.close();
+});
+
+Deno.test({
+ permissions: { net: true },
+}, function netTcpListenDoesNotThrowOnStringPort() {
+ // @ts-ignore String port is not allowed by typing, but it shouldn't throw
+ // for backwards compatibility.
+ const listener = Deno.listen({ hostname: "localhost", port: "0" });
+ listener.close();
+});
+
+Deno.test(
+ { permissions: { net: true } },
+ async function listenerExplicitResourceManagement() {
+ let done: Promise<Deno.errors.BadResource>;
+
+ {
+ using listener = Deno.listen({ port: listenPort });
+
+ done = assertRejects(
+ () => listener.accept(),
+ Deno.errors.BadResource,
+ );
+ }
+
+ await done;
+ },
+);
+
+Deno.test(
+ { permissions: { net: true } },
+ async function listenerExplicitResourceManagementManualClose() {
+ using listener = Deno.listen({ port: listenPort });
+ listener.close();
+ await assertRejects( // definitely closed
+ () => listener.accept(),
+ Deno.errors.BadResource,
+ );
+ // calling [Symbol.dispose] after manual close is a no-op
+ },
+);