From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- .../test_tcpsocket_client_and_server_basics.js | 423 +++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 dom/network/tests/test_tcpsocket_client_and_server_basics.js (limited to 'dom/network/tests/test_tcpsocket_client_and_server_basics.js') diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.js b/dom/network/tests/test_tcpsocket_client_and_server_basics.js new file mode 100644 index 000000000..f47689b90 --- /dev/null +++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js @@ -0,0 +1,423 @@ +'use strict'; + +const SERVER_BACKLOG = -1; + +const SOCKET_EVENTS = ['open', 'data', 'drain', 'error', 'close']; + +function concatUint8Arrays(a, b) { + let newArr = new Uint8Array(a.length + b.length); + newArr.set(a, 0); + newArr.set(b, a.length); + return newArr; +} + +function assertUint8ArraysEqual(a, b, comparingWhat) { + if (a.length !== b.length) { + ok(false, comparingWhat + ' arrays do not have the same length; ' + + a.length + ' versus ' + b.length); + return; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + ok(false, comparingWhat + ' arrays differ at index ' + i + + a[i] + ' versus ' + b[i]); + return; + } + } + ok(true, comparingWhat + ' arrays were equivalent.'); +} + +/** + * Helper method to add event listeners to a socket and provide two Promise-returning + * helpers (see below for docs on them). This *must* be called during the turn of + * the event loop where TCPSocket's constructor is called or the onconnect method is being + * invoked. + */ +function listenForEventsOnSocket(socket, socketType) { + let wantDataLength = null; + let wantDataAndClose = false; + let pendingResolve = null; + let receivedEvents = []; + let receivedData = null; + let handleGenericEvent = function(event) { + dump('(' + socketType + ' event: ' + event.type + ')\n'); + if (pendingResolve && wantDataLength === null) { + pendingResolve(event); + pendingResolve = null; + } else { + receivedEvents.push(event); + } + }; + + socket.onopen = handleGenericEvent; + socket.ondrain = handleGenericEvent; + socket.onerror = handleGenericEvent; + socket.onclose = function(event) { + if (!wantDataAndClose) { + handleGenericEvent(event); + } else if (pendingResolve) { + dump('(' + socketType + ' event: close)\n'); + pendingResolve(receivedData); + pendingResolve = null; + wantDataAndClose = false; + } + } + socket.ondata = function(event) { + dump('(' + socketType + ' event: ' + event.type + ' length: ' + + event.data.byteLength + ')\n'); + ok(socketCompartmentInstanceOfArrayBuffer(event.data), + 'payload is ArrayBuffer'); + var arr = new Uint8Array(event.data); + if (receivedData === null) { + receivedData = arr; + } else { + receivedData = concatUint8Arrays(receivedData, arr); + } + if (wantDataLength !== null && + receivedData.length >= wantDataLength) { + pendingResolve(receivedData); + pendingResolve = null; + receivedData = null; + wantDataLength = null; + } + }; + + + return { + /** + * Return a Promise that will be resolved with the next (non-data) event + * received by the socket. If there are queued events, the Promise will + * be immediately resolved (but you won't see that until a future turn of + * the event loop). + */ + waitForEvent: function() { + if (pendingResolve) { + throw new Error('only one wait allowed at a time.'); + } + + if (receivedEvents.length) { + return Promise.resolve(receivedEvents.shift()); + } + + dump('(' + socketType + ' waiting for event)\n'); + return new Promise(function(resolve, reject) { + pendingResolve = resolve; + }); + }, + /** + * Return a Promise that will be resolved with a Uint8Array of at least the + * given length. We buffer / accumulate received data until we have enough + * data. Data is buffered even before you call this method, so be sure to + * explicitly wait for any and all data sent by the other side. + */ + waitForDataWithAtLeastLength: function(length) { + if (pendingResolve) { + throw new Error('only one wait allowed at a time.'); + } + if (receivedData && receivedData.length >= length) { + let promise = Promise.resolve(receivedData); + receivedData = null; + return promise; + } + dump('(' + socketType + ' waiting for ' + length + ' bytes)\n'); + return new Promise(function(resolve, reject) { + pendingResolve = resolve; + wantDataLength = length; + }); + }, + waitForAnyDataAndClose: function() { + if (pendingResolve) { + throw new Error('only one wait allowed at a time.'); + } + + return new Promise(function(resolve, reject) { + pendingResolve = resolve; + // we may receive no data before getting close, in which case we want to + // return an empty array + receivedData = new Uint8Array(); + wantDataAndClose = true; + }); + } + }; +} + +/** + * Return a promise that is resolved when the server receives a connection. The + * promise is resolved with { socket, queue } where `queue` is the result of + * calling listenForEventsOnSocket(socket). This must be done because we need + * to add the event listener during the connection. + */ +function waitForConnection(listeningServer) { + return new Promise(function(resolve, reject) { + // Because of the event model of sockets, we can't use the + // listenForEventsOnSocket mechanism; we need to hook up listeners during + // the connect event. + listeningServer.onconnect = function(event) { + // Clobber the listener to get upset if it receives any more connections + // after this. + listeningServer.onconnect = function() { + ok(false, 'Received a connection when not expecting one.'); + }; + ok(true, 'Listening server accepted socket'); + resolve({ + socket: event.socket, + queue: listenForEventsOnSocket(event.socket, 'server') + }); + }; + }); +} + +function defer() { + var deferred = {}; + deferred.promise = new Promise(function(resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; +} + + +function* test_basics() { + // See bug 903830; in e10s mode we never get to find out the localPort if we + // let it pick a free port by choosing 0. This is the same port the xpcshell + // test was using. + let serverPort = 8085; + + // - Start up a listening socket. + let listeningServer = createServer(serverPort, + { binaryType: 'arraybuffer' }, + SERVER_BACKLOG); + + let connectedPromise = waitForConnection(listeningServer); + + // -- Open a connection to the server + let clientSocket = createSocket('127.0.0.1', serverPort, + { binaryType: 'arraybuffer' }); + let clientQueue = listenForEventsOnSocket(clientSocket, 'client'); + + // (the client connects) + is((yield clientQueue.waitForEvent()).type, 'open', 'got open event'); + is(clientSocket.readyState, 'open', 'client readyState is open'); + + // (the server connected) + let { socket: serverSocket, queue: serverQueue } = yield connectedPromise; + is(serverSocket.readyState, 'open', 'server readyState is open'); + + // -- Simple send / receive + // - Send data from client to server + // (But not so much we cross the drain threshold.) + let smallUint8Array = new Uint8Array(256); + for (let i = 0; i < smallUint8Array.length; i++) { + smallUint8Array[i] = i; + } + is(clientSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length), true, + 'Client sending less than 64k, buffer should not be full.'); + + let serverReceived = yield serverQueue.waitForDataWithAtLeastLength(256); + assertUint8ArraysEqual(serverReceived, smallUint8Array, + 'Server received/client sent'); + + // - Send data from server to client + // (But not so much we cross the drain threshold.) + is(serverSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length), true, + 'Server sending less than 64k, buffer should not be full.'); + + let clientReceived = yield clientQueue.waitForDataWithAtLeastLength(256); + assertUint8ArraysEqual(clientReceived, smallUint8Array, + 'Client received/server sent'); + + // -- Perform sending multiple times with different buffer slices + // - Send data from client to server + // (But not so much we cross the drain threshold.) + is(clientSocket.send(smallUint8Array.buffer, 0, 7), + true, 'Client sending less than 64k, buffer should not be full.'); + is(clientSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7), + true, 'Client sending less than 64k, buffer should not be full.'); + + serverReceived = yield serverQueue.waitForDataWithAtLeastLength(256); + assertUint8ArraysEqual(serverReceived, smallUint8Array, + 'Server received/client sent'); + + // - Send data from server to client + // (But not so much we cross the drain threshold.) + is(serverSocket.send(smallUint8Array.buffer, 0, 7), + true, 'Server sending less than 64k, buffer should not be full.'); + is(serverSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7), + true, 'Server sending less than 64k, buffer should not be full.'); + + clientReceived = yield clientQueue.waitForDataWithAtLeastLength(256); + assertUint8ArraysEqual(clientReceived, smallUint8Array, + 'Client received/server sent'); + + + // -- Send "big" data in both directions + // (Enough to cross the buffering/drain threshold; 64KiB) + let bigUint8Array = new Uint8Array(65536 + 3); + for (let i = 0; i < bigUint8Array.length; i++) { + bigUint8Array[i] = i % 256; + } + // Do this twice so we have confidence that the 'drain' event machinery + // doesn't break after the first use. + for (let iSend = 0; iSend < 2; iSend++) { + // - Send "big" data from the client to the server + is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false, + 'Client sending more than 64k should result in the buffer being full.'); + is((yield clientQueue.waitForEvent()).type, 'drain', + 'The drain event should fire after a large send that indicated full.'); + + serverReceived = yield serverQueue.waitForDataWithAtLeastLength( + bigUint8Array.length); + assertUint8ArraysEqual(serverReceived, bigUint8Array, + 'server received/client sent'); + + // - Send "big" data from the server to the client + is(serverSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false, + 'Server sending more than 64k should result in the buffer being full.'); + is((yield serverQueue.waitForEvent()).type, 'drain', + 'The drain event should fire after a large send that indicated full.'); + + clientReceived = yield clientQueue.waitForDataWithAtLeastLength( + bigUint8Array.length); + assertUint8ArraysEqual(clientReceived, bigUint8Array, + 'client received/server sent'); + } + + // -- Server closes the connection + serverSocket.close(); + is(serverSocket.readyState, 'closing', + 'readyState should be closing immediately after calling close'); + + is((yield clientQueue.waitForEvent()).type, 'close', + 'The client should get a close event when the server closes.'); + is(clientSocket.readyState, 'closed', + 'client readyState should be closed after close event'); + is((yield serverQueue.waitForEvent()).type, 'close', + 'The server should get a close event when it closes itself.'); + is(serverSocket.readyState, 'closed', + 'server readyState should be closed after close event'); + + // -- Re-establish connection + connectedPromise = waitForConnection(listeningServer); + clientSocket = createSocket('127.0.0.1', serverPort, + { binaryType: 'arraybuffer' }); + clientQueue = listenForEventsOnSocket(clientSocket, 'client'); + is((yield clientQueue.waitForEvent()).type, 'open', 'got open event'); + + let connectedResult = yield connectedPromise; + // destructuring assignment is not yet ES6 compliant, must manually unpack + serverSocket = connectedResult.socket; + serverQueue = connectedResult.queue; + + // -- Client closes the connection + clientSocket.close(); + is(clientSocket.readyState, 'closing', + 'client readyState should be losing immediately after calling close'); + + is((yield clientQueue.waitForEvent()).type, 'close', + 'The client should get a close event when it closes itself.'); + is(clientSocket.readyState, 'closed', + 'client readyState should be closed after the close event is received'); + is((yield serverQueue.waitForEvent()).type, 'close', + 'The server should get a close event when the client closes.'); + is(serverSocket.readyState, 'closed', + 'server readyState should be closed after the close event is received'); + + + // -- Re-establish connection + connectedPromise = waitForConnection(listeningServer); + clientSocket = createSocket('127.0.0.1', serverPort, + { binaryType: 'arraybuffer' }); + clientQueue = listenForEventsOnSocket(clientSocket, 'client'); + is((yield clientQueue.waitForEvent()).type, 'open', 'got open event'); + + connectedResult = yield connectedPromise; + // destructuring assignment is not yet ES6 compliant, must manually unpack + serverSocket = connectedResult.socket; + serverQueue = connectedResult.queue; + + // -- Call close after enqueueing a lot of data, make sure it goes through. + // We'll have the client send and close. + is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false, + 'Client sending more than 64k should result in the buffer being full.'); + clientSocket.close(); + // The drain will still fire + is((yield clientQueue.waitForEvent()).type, 'drain', + 'The drain event should fire after a large send that returned true.'); + // Then we'll get a close + is((yield clientQueue.waitForEvent()).type, 'close', + 'The close event should fire after the drain event.'); + + // The server will get its data + serverReceived = yield serverQueue.waitForDataWithAtLeastLength( + bigUint8Array.length); + assertUint8ArraysEqual(serverReceived, bigUint8Array, + 'server received/client sent'); + // And a close. + is((yield serverQueue.waitForEvent()).type, 'close', + 'The drain event should fire after a large send that returned true.'); + + + // -- Re-establish connection + connectedPromise = waitForConnection(listeningServer); + clientSocket = createSocket('127.0.0.1', serverPort, + { binaryType: 'string' }); + clientQueue = listenForEventsOnSocket(clientSocket, 'client'); + is((yield clientQueue.waitForEvent()).type, 'open', 'got open event'); + + connectedResult = yield connectedPromise; + // destructuring assignment is not yet ES6 compliant, must manually unpack + serverSocket = connectedResult.socket; + serverQueue = connectedResult.queue; + + // -- Attempt to send non-string data. + // Restore the original behavior by replacing toString with + // Object.prototype.toString. (bug 1121938) + bigUint8Array.toString = Object.prototype.toString; + is(clientSocket.send(bigUint8Array), true, + 'Client sending a large non-string should only send a small string.'); + clientSocket.close(); + // The server will get its data + serverReceived = yield serverQueue.waitForDataWithAtLeastLength( + bigUint8Array.toString().length); + // Then we'll get a close + is((yield clientQueue.waitForEvent()).type, 'close', + 'The close event should fire after the drain event.'); + + // -- Re-establish connection (Test for Close Immediately) + connectedPromise = waitForConnection(listeningServer); + clientSocket = createSocket('127.0.0.1', serverPort, + { binaryType: 'arraybuffer' }); + clientQueue = listenForEventsOnSocket(clientSocket, 'client'); + is((yield clientQueue.waitForEvent()).type, 'open', 'got open event'); + + connectedResult = yield connectedPromise; + // destructuring assignment is not yet ES6 compliant, must manually unpack + serverSocket = connectedResult.socket; + serverQueue = connectedResult.queue; + + // -- Attempt to send two non-string data. + is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false, + 'Server sending more than 64k should result in the buffer being full.'); + is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false, + 'Server sending more than 64k should result in the buffer being full.'); + clientSocket.closeImmediately(); + + serverReceived = yield serverQueue.waitForAnyDataAndClose(); + + is(serverReceived.length < (2 * bigUint8Array.length), true, 'Received array length less than sent array length'); + + // -- Close the listening server (and try to connect) + // We want to verify that the server actually closes / stops listening when + // we tell it to. + listeningServer.close(); + + // - try and connect, get an error + clientSocket = createSocket('127.0.0.1', serverPort, + { binaryType: 'arraybuffer' }); + clientQueue = listenForEventsOnSocket(clientSocket, 'client'); + is((yield clientQueue.waitForEvent()).type, 'error', 'fail to connect'); + is(clientSocket.readyState, 'closed', + 'client readyState should be closed after the failure to connect'); +} + +add_task(test_basics); -- cgit v1.2.3