michael@0: /** michael@0: * Test TCPSocket.js by creating an XPCOM-style server socket, then sending michael@0: * data in both directions and making sure each side receives their data michael@0: * correctly and with the proper events. michael@0: * michael@0: * This test is derived from netwerk/test/unit/test_socks.js, except we don't michael@0: * involve a subprocess. michael@0: * michael@0: * Future work: michael@0: * - SSL. see https://bugzilla.mozilla.org/show_bug.cgi?id=466524 michael@0: * https://bugzilla.mozilla.org/show_bug.cgi?id=662180 michael@0: * Alternatively, mochitests could be used. michael@0: * - Testing overflow logic. michael@0: * michael@0: **/ michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: const CC = Components.Constructor; michael@0: michael@0: /** michael@0: * michael@0: * Constants michael@0: * michael@0: */ michael@0: michael@0: // Test parameter. michael@0: const PORT = 8085; michael@0: const BACKLOG = -1; michael@0: michael@0: // Some binary data to send. michael@0: const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0], michael@0: DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length), michael@0: TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER), michael@0: HELLO_WORLD = "hlo wrld. ", michael@0: BIG_ARRAY = new Array(65539); michael@0: michael@0: TYPED_DATA_ARRAY.set(DATA_ARRAY, 0); michael@0: michael@0: for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) { michael@0: BIG_ARRAY[i_big] = Math.floor(Math.random() * 256); michael@0: } michael@0: michael@0: const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length); michael@0: const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER); michael@0: BIG_TYPED_ARRAY.set(BIG_ARRAY); michael@0: michael@0: const TCPSocket = new (CC("@mozilla.org/tcp-socket;1", michael@0: "nsIDOMTCPSocket"))(); michael@0: michael@0: const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) michael@0: .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: /** michael@0: * michael@0: * Helper functions michael@0: * michael@0: */ michael@0: michael@0: michael@0: function makeSuccessCase(name) { michael@0: return function() { michael@0: do_print('got expected: ' + name); michael@0: run_next_test(); michael@0: }; michael@0: } michael@0: michael@0: function makeJointSuccess(names) { michael@0: let funcs = {}, successCount = 0; michael@0: names.forEach(function(name) { michael@0: funcs[name] = function() { michael@0: do_print('got expected: ' + name); michael@0: if (++successCount === names.length) michael@0: run_next_test(); michael@0: }; michael@0: }); michael@0: return funcs; michael@0: } michael@0: michael@0: function makeFailureCase(name) { michael@0: return function() { michael@0: let argstr; michael@0: if (arguments.length) { michael@0: argstr = '(args: ' + michael@0: Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')'; michael@0: } michael@0: else { michael@0: argstr = '(no arguments)'; michael@0: } michael@0: do_throw('got unexpected: ' + name + ' ' + argstr); michael@0: }; michael@0: } michael@0: michael@0: function makeExpectData(name, expectedData, fromEvent, callback) { michael@0: let dataBuffer = fromEvent ? null : [], done = false; michael@0: let dataBufferView = null; michael@0: return function(receivedData) { michael@0: if (receivedData.data) { michael@0: receivedData = receivedData.data; michael@0: } michael@0: let recvLength = receivedData.byteLength !== undefined ? michael@0: receivedData.byteLength : receivedData.length; michael@0: michael@0: if (fromEvent) { michael@0: if (dataBuffer) { michael@0: let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength); michael@0: let newBufferView = new Uint8Array(newBuffer); michael@0: newBufferView.set(dataBufferView, 0); michael@0: newBufferView.set(receivedData, dataBuffer.byteLength); michael@0: dataBuffer = newBuffer; michael@0: dataBufferView = newBufferView; michael@0: } michael@0: else { michael@0: dataBuffer = receivedData; michael@0: dataBufferView = new Uint8Array(dataBuffer); michael@0: } michael@0: } michael@0: else { michael@0: dataBuffer = dataBuffer.concat(receivedData); michael@0: } michael@0: do_print(name + ' received ' + recvLength + ' bytes'); michael@0: michael@0: if (done) michael@0: do_throw(name + ' Received data event when already done!'); michael@0: michael@0: let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer; michael@0: if (dataView.length >= expectedData.length) { michael@0: // check the bytes are equivalent michael@0: for (let i = 0; i < expectedData.length; i++) { michael@0: if (dataView[i] !== expectedData[i]) { michael@0: do_throw(name + ' Received mismatched character at position ' + i); michael@0: } michael@0: } michael@0: if (dataView.length > expectedData.length) michael@0: do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' + michael@0: expectedData.length + ' bytes.'); michael@0: michael@0: done = true; michael@0: if (callback) { michael@0: callback(); michael@0: } else { michael@0: run_next_test(); michael@0: } michael@0: } michael@0: }; michael@0: } michael@0: michael@0: var server = null, sock = null, connectedsock = null, failure_drain = null; michael@0: var count = 0; michael@0: /** michael@0: * michael@0: * Test functions michael@0: * michael@0: */ michael@0: michael@0: /** michael@0: * Connect the socket to the server. This test is added as the first michael@0: * test, and is also added after every test which results in the socket michael@0: * being closed. michael@0: */ michael@0: michael@0: function connectSock() { michael@0: if (server) { michael@0: server.close(); michael@0: } michael@0: michael@0: var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']); michael@0: var options = { binaryType: 'arraybuffer' }; michael@0: michael@0: server = TCPSocket.listen(PORT, options, BACKLOG); michael@0: server.onconnect = function(socket) { michael@0: connectedsock = socket; michael@0: connectedsock.ondata = makeFailureCase('serverdata'); michael@0: connectedsock.onerror = makeFailureCase('servererror'); michael@0: connectedsock.onclose = makeFailureCase('serverclose'); michael@0: yayFuncs.serveropen(); michael@0: }; michael@0: server.onerror = makeFailureCase('error'); michael@0: sock = TCPSocket.open( michael@0: '127.0.0.1', PORT, options); michael@0: sock.onopen = yayFuncs.clientopen; michael@0: sock.ondrain = null; michael@0: sock.ondata = makeFailureCase('data'); michael@0: sock.onerror = makeFailureCase('error'); michael@0: sock.onclose = makeFailureCase('close'); michael@0: } michael@0: michael@0: /** michael@0: * Connect the socket to the server after the server was closed. michael@0: * This test is added after test to close the server was conducted. michael@0: */ michael@0: function openSockInClosingServer() { michael@0: var success = makeSuccessCase('clientnotopen'); michael@0: var options = { binaryType: 'arraybuffer' }; michael@0: michael@0: sock = TCPSocket.open( michael@0: '127.0.0.1', PORT, options); michael@0: michael@0: sock.onopen = makeFailureCase('open'); michael@0: sock.onerror = success; michael@0: } michael@0: michael@0: /** michael@0: * Test that sending a small amount of data works, and that buffering michael@0: * does not take place for this small amount of data. michael@0: */ michael@0: michael@0: function sendDataToServer() { michael@0: connectedsock.ondata = makeExpectData('serverdata', DATA_ARRAY, true); michael@0: if (!sock.send(DATA_ARRAY_BUFFER)) { michael@0: do_throw("send should not have buffered such a small amount of data"); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Test that data sent from the server correctly fires the ondata michael@0: * callback on the client side. michael@0: */ michael@0: michael@0: function receiveDataFromServer() { michael@0: connectedsock.ondata = makeFailureCase('serverdata'); michael@0: sock.ondata = makeExpectData('data', DATA_ARRAY, true); michael@0: michael@0: connectedsock.send(DATA_ARRAY_BUFFER); michael@0: } michael@0: michael@0: /** michael@0: * Test that when the server closes the connection, the onclose callback michael@0: * is fired on the client side. michael@0: */ michael@0: michael@0: function serverCloses() { michael@0: // we don't really care about the server's close event, but we do want to michael@0: // make sure it happened for sequencing purposes. michael@0: sock.ondata = makeFailureCase('data'); michael@0: sock.onclose = makeFailureCase('close1'); michael@0: connectedsock.onclose = makeFailureCase('close2'); michael@0: michael@0: server.close(); michael@0: run_next_test(); michael@0: } michael@0: michael@0: /** michael@0: * Test that when the client closes the connection, the onclose callback michael@0: * is fired on the server side. michael@0: */ michael@0: michael@0: function cleanup() { michael@0: do_print("Cleaning up"); michael@0: sock.onclose = null; michael@0: connectedsock.onclose = null; michael@0: michael@0: server.close(); michael@0: sock.close(); michael@0: if (count == 1){ michael@0: if (!gInChild) michael@0: Services.prefs.clearUserPref('dom.mozTCPSocket.enabled'); michael@0: } michael@0: count++; michael@0: run_next_test(); michael@0: } michael@0: // - connect, data and events work both ways michael@0: add_test(connectSock); michael@0: michael@0: add_test(sendDataToServer); michael@0: michael@0: add_test(receiveDataFromServer); michael@0: // - server closes on us michael@0: add_test(serverCloses); michael@0: michael@0: // - send and receive after closing server michael@0: add_test(sendDataToServer); michael@0: add_test(receiveDataFromServer); michael@0: // - check a connection refused from client to server after closing server michael@0: add_test(serverCloses); michael@0: michael@0: add_test(openSockInClosingServer); michael@0: michael@0: // - clean up michael@0: add_test(cleanup); michael@0: michael@0: // - send and receive in reverse order for client and server michael@0: add_test(connectSock); michael@0: michael@0: add_test(receiveDataFromServer); michael@0: michael@0: add_test(sendDataToServer); michael@0: michael@0: // - clean up michael@0: michael@0: add_test(cleanup); michael@0: michael@0: function run_test() { michael@0: if (!gInChild) michael@0: Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true); michael@0: michael@0: run_next_test(); michael@0: }