From b7e06a979a3948e67005f439851cd2d080bcaea7 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 10 Oct 2024 16:48:01 -0700 Subject: [PATCH] Add some more node tests --- .../node/test/parallel/http-hex-write.test.js | 60 +++++++++++ .../parallel/http-keepalive-request.test.js | 99 +++++++++++++++++++ ...-pipeline-requests-connection-leak.test.js | 50 ++++++++++ .../parallel/http-readable-data-event.test.js | 75 ++++++++++++++ ...-response-remove-header-after-sent.test.js | 30 ++++++ 5 files changed, 314 insertions(+) create mode 100644 test/js/node/test/parallel/http-hex-write.test.js create mode 100644 test/js/node/test/parallel/http-keepalive-request.test.js create mode 100644 test/js/node/test/parallel/http-pipeline-requests-connection-leak.test.js create mode 100644 test/js/node/test/parallel/http-readable-data-event.test.js create mode 100644 test/js/node/test/parallel/http-response-remove-header-after-sent.test.js diff --git a/test/js/node/test/parallel/http-hex-write.test.js b/test/js/node/test/parallel/http-hex-write.test.js new file mode 100644 index 0000000000..1606bcac18 --- /dev/null +++ b/test/js/node/test/parallel/http-hex-write.test.js @@ -0,0 +1,60 @@ +//#FILE: test-http-hex-write.js +//#SHA1: 77a5322a8fe08e8505f39d42614167d223c9fbb0 +//----------------- +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; +const http = require("http"); + +const expectedResponse = "hex\nutf8\n"; + +test("HTTP server writes hex and utf8", async () => { + const server = http.createServer((req, res) => { + res.setHeader("content-length", expectedResponse.length); + res.write("6865780a", "hex"); + res.write("utf8\n"); + res.end(); + server.close(); + }); + + await new Promise(resolve => { + server.listen(0, () => { + const port = server.address().port; + http + .request({ port }) + .on("response", res => { + let data = ""; + res.setEncoding("ascii"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe(expectedResponse); + resolve(); + }); + }) + .end(); + }); + }); +}); + +//<#END_FILE: test-http-hex-write.js diff --git a/test/js/node/test/parallel/http-keepalive-request.test.js b/test/js/node/test/parallel/http-keepalive-request.test.js new file mode 100644 index 0000000000..956c421a4a --- /dev/null +++ b/test/js/node/test/parallel/http-keepalive-request.test.js @@ -0,0 +1,99 @@ +//#FILE: test-http-keepalive-request.js +//#SHA1: 31cc9b875e1ead9a0b98fb07b672b536f6d06fba +//----------------- +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +const http = require("http"); + +let serverSocket = null; +let clientSocket = null; +const expectRequests = 10; +let actualRequests = 0; + +const server = http.createServer((req, res) => { + // They should all come in on the same server socket. + if (serverSocket) { + expect(req.socket).toBe(serverSocket); + } else { + serverSocket = req.socket; + } + + res.end(req.url); +}); + +const agent = new http.Agent({ keepAlive: true }); + +function makeRequest(n) { + return new Promise(resolve => { + if (n === 0) { + server.close(); + agent.destroy(); + resolve(); + return; + } + + const req = http.request({ + port: server.address().port, + path: `/${n}`, + agent: agent, + }); + + req.end(); + + req.on("socket", sock => { + if (clientSocket) { + expect(sock).toBe(clientSocket); + } else { + clientSocket = sock; + } + }); + + req.on("response", res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", c => { + data += c; + }); + res.on("end", () => { + expect(data).toBe(`/${n}`); + setTimeout(() => { + actualRequests++; + resolve(makeRequest(n - 1)); + }, 1); + }); + }); + }); +} + +test("HTTP keep-alive requests", async () => { + await new Promise(resolve => { + server.listen(0, () => { + resolve(makeRequest(expectRequests)); + }); + }); + + expect(actualRequests).toBe(expectRequests); +}); + +//<#END_FILE: test-http-keepalive-request.js diff --git a/test/js/node/test/parallel/http-pipeline-requests-connection-leak.test.js b/test/js/node/test/parallel/http-pipeline-requests-connection-leak.test.js new file mode 100644 index 0000000000..23234f80bc --- /dev/null +++ b/test/js/node/test/parallel/http-pipeline-requests-connection-leak.test.js @@ -0,0 +1,50 @@ +//#FILE: test-http-pipeline-requests-connection-leak.js +//#SHA1: fc3e33a724cc7a499c7716fe8af6b78e7f72e943 +//----------------- +"use strict"; + +const http = require("http"); +const net = require("net"); + +const big = Buffer.alloc(16 * 1024, "A"); + +const COUNT = 1e4; + +test("HTTP pipeline requests do not cause connection leak", done => { + let client; + const server = http.createServer((req, res) => { + res.end(big, () => { + countdown.dec(); + }); + }); + + const countdown = new Countdown(COUNT, () => { + server.close(); + client.end(); + done(); + }); + + server.listen(0, () => { + const req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".repeat(COUNT); + client = net.connect(server.address().port, () => { + client.write(req); + }); + client.resume(); + }); +}); + +class Countdown { + constructor(count, callback) { + this.count = count; + this.callback = callback; + } + + dec() { + this.count--; + if (this.count === 0) { + this.callback(); + } + } +} + +//<#END_FILE: test-http-pipeline-requests-connection-leak.js diff --git a/test/js/node/test/parallel/http-readable-data-event.test.js b/test/js/node/test/parallel/http-readable-data-event.test.js new file mode 100644 index 0000000000..238c44878b --- /dev/null +++ b/test/js/node/test/parallel/http-readable-data-event.test.js @@ -0,0 +1,75 @@ +//#FILE: test-http-readable-data-event.js +//#SHA1: a094638e155550b5bc5ecdf5b11c54e8124d1dc1 +//----------------- +"use strict"; + +const http = require("http"); +const helloWorld = "Hello World!"; +const helloAgainLater = "Hello again later!"; + +let next = null; + +test("HTTP readable data event", async () => { + const server = http.createServer((req, res) => { + res.writeHead(200, { + "Content-Length": `${helloWorld.length + helloAgainLater.length}`, + }); + + // We need to make sure the data is flushed + // before writing again + next = () => { + res.end(helloAgainLater); + next = () => {}; + }; + + res.write(helloWorld); + }); + + await new Promise(resolve => server.listen(0, resolve)); + + const opts = { + hostname: "localhost", + port: server.address().port, + path: "/", + }; + + const expectedData = [helloWorld, helloAgainLater]; + const expectedRead = [helloWorld, null, helloAgainLater, null, null]; + + await new Promise((resolve, reject) => { + const req = http.request(opts, res => { + res.on("error", reject); + + const readableSpy = jest.fn(); + res.on("readable", readableSpy); + + res.on("readable", () => { + let data; + + do { + data = res.read(); + expect(data).toBe(expectedRead.shift()); + next(); + } while (data !== null); + }); + + res.setEncoding("utf8"); + const dataSpy = jest.fn(); + res.on("data", dataSpy); + + res.on("data", data => { + expect(data).toBe(expectedData.shift()); + }); + + res.on("end", () => { + expect(readableSpy).toHaveBeenCalledTimes(3); + expect(dataSpy).toHaveBeenCalledTimes(2); + server.close(() => resolve()); + }); + }); + + req.end(); + }); +}); + +//<#END_FILE: test-http-readable-data-event.js diff --git a/test/js/node/test/parallel/http-response-remove-header-after-sent.test.js b/test/js/node/test/parallel/http-response-remove-header-after-sent.test.js new file mode 100644 index 0000000000..c01808e440 --- /dev/null +++ b/test/js/node/test/parallel/http-response-remove-header-after-sent.test.js @@ -0,0 +1,30 @@ +//#FILE: test-http-response-remove-header-after-sent.js +//#SHA1: df9a9a2f545c88b70d6d33252a1568339ea6f5b3 +//----------------- +"use strict"; + +const http = require("http"); + +test("remove header after response is sent", done => { + const server = http.createServer((req, res) => { + res.removeHeader("header1", 1); + res.write("abc"); + expect(() => res.removeHeader("header2", 2)).toThrow( + expect.objectContaining({ + code: "ERR_HTTP_HEADERS_SENT", + name: "Error", + message: expect.any(String), + }), + ); + res.end(); + }); + + server.listen(0, () => { + http.get({ port: server.address().port }, () => { + server.close(); + done(); + }); + }); +}); + +//<#END_FILE: test-http-response-remove-header-after-sent.js