michael@0: const CC = Components.Constructor; michael@0: michael@0: const ServerSocket = CC("@mozilla.org/network/server-socket;1", michael@0: "nsIServerSocket", michael@0: "init"); michael@0: michael@0: /** michael@0: * TestServer: A single instance of this is created as |serv|. When created, michael@0: * it starts listening on the loopback address on port |serv.port|. Tests will michael@0: * connect to it after setting |serv.acceptCallback|, which is invoked after it michael@0: * accepts a connection. michael@0: * michael@0: * Within |serv.acceptCallback|, various properties of |serv| can be used to michael@0: * run checks. After the callback, the connection is closed, but the server michael@0: * remains listening until |serv.stop| michael@0: * michael@0: * Note: TestServer can only handle a single connection at a time. Tests michael@0: * should use run_next_test at the end of |serv.acceptCallback| to start the michael@0: * following test which creates a connection. michael@0: */ michael@0: function TestServer() { michael@0: this.reset(); michael@0: michael@0: // start server. michael@0: // any port (-1), loopback only (true), default backlog (-1) michael@0: this.listener = ServerSocket(-1, true, -1); michael@0: this.port = this.listener.port; michael@0: do_print('server: listening on', this.port); michael@0: this.listener.asyncListen(this); michael@0: } michael@0: michael@0: TestServer.prototype = { michael@0: onSocketAccepted: function(socket, trans) { michael@0: do_print('server: got client connection'); michael@0: michael@0: // one connection at a time. michael@0: if (this.input !== null) { michael@0: try { socket.close(); } catch(ignore) {} michael@0: do_throw("Test written to handle one connection at a time."); michael@0: } michael@0: michael@0: try { michael@0: this.input = trans.openInputStream(0, 0, 0); michael@0: this.output = trans.openOutputStream(0, 0, 0); michael@0: this.selfAddr = trans.getScriptableSelfAddr(); michael@0: this.peerAddr = trans.getScriptablePeerAddr(); michael@0: michael@0: this.acceptCallback(); michael@0: } catch(e) { michael@0: /* In a native callback such as onSocketAccepted, exceptions might not michael@0: * get output correctly or logged to test output. Send them through michael@0: * do_throw, which fails the test immediately. */ michael@0: do_report_unexpected_exception(e, "in TestServer.onSocketAccepted"); michael@0: } michael@0: michael@0: this.reset(); michael@0: } , michael@0: michael@0: onStopListening: function(socket) {} , michael@0: michael@0: /** michael@0: * Called to close a connection and clean up properties. michael@0: */ michael@0: reset: function() { michael@0: if (this.input) michael@0: try { this.input.close(); } catch(ignore) {} michael@0: if (this.output) michael@0: try { this.output.close(); } catch(ignore) {} michael@0: michael@0: this.input = null; michael@0: this.output = null; michael@0: this.acceptCallback = null; michael@0: this.selfAddr = null; michael@0: this.peerAddr = null; michael@0: } , michael@0: michael@0: /** michael@0: * Cleanup for TestServer and this test case. michael@0: */ michael@0: stop: function() { michael@0: this.reset(); michael@0: try { this.listener.close(); } catch(ignore) {} michael@0: } michael@0: }; michael@0: michael@0: michael@0: /** michael@0: * Helper function. michael@0: * Compares two nsINetAddr objects and ensures they are logically equivalent. michael@0: */ michael@0: function checkAddrEqual(lhs, rhs) { michael@0: do_check_eq(lhs.family, rhs.family); michael@0: michael@0: if (lhs.family === Ci.nsINetAddr.FAMILY_INET) { michael@0: do_check_eq(lhs.address, rhs.address); michael@0: do_check_eq(lhs.port, rhs.port); michael@0: } michael@0: michael@0: /* TODO: fully support ipv6 and local */ michael@0: } michael@0: michael@0: michael@0: /** michael@0: * An instance of SocketTransportService, used to create connections. michael@0: */ michael@0: var sts; michael@0: michael@0: /** michael@0: * Single instance of TestServer michael@0: */ michael@0: var serv; michael@0: michael@0: /** michael@0: * Connections have 5 seconds to be made, or a timeout function fails this michael@0: * test. This prevents the test from hanging and bringing down the entire michael@0: * xpcshell test chain. michael@0: */ michael@0: var connectTimeout = 5*1000; michael@0: michael@0: /** michael@0: * A place for individual tests to place Objects of importance for access michael@0: * throughout asynchronous testing. Particularly important for any output or michael@0: * input streams opened, as cleanup of those objects (by the garbage collector) michael@0: * causes the stream to close and may have other side effects. michael@0: */ michael@0: var testDataStore = null; michael@0: michael@0: /** michael@0: * IPv4 test. michael@0: */ michael@0: function testIpv4() { michael@0: testDataStore = { michael@0: transport : null , michael@0: ouput : null michael@0: } michael@0: michael@0: serv.acceptCallback = function() { michael@0: // disable the timeoutCallback michael@0: serv.timeoutCallback = function(){}; michael@0: michael@0: var selfAddr = testDataStore.transport.getScriptableSelfAddr(); michael@0: var peerAddr = testDataStore.transport.getScriptablePeerAddr(); michael@0: michael@0: // check peerAddr against expected values michael@0: do_check_eq(peerAddr.family, Ci.nsINetAddr.FAMILY_INET); michael@0: do_check_eq(peerAddr.port, testDataStore.transport.port); michael@0: do_check_eq(peerAddr.port, serv.port); michael@0: do_check_eq(peerAddr.address, "127.0.0.1"); michael@0: michael@0: // check selfAddr against expected values michael@0: do_check_eq(selfAddr.family, Ci.nsINetAddr.FAMILY_INET); michael@0: do_check_eq(selfAddr.address, "127.0.0.1"); michael@0: michael@0: // check that selfAddr = server.peerAddr and vice versa. michael@0: checkAddrEqual(selfAddr, serv.peerAddr); michael@0: checkAddrEqual(peerAddr, serv.selfAddr); michael@0: michael@0: testDataStore = null; michael@0: do_execute_soon(run_next_test); michael@0: }; michael@0: michael@0: // Useful timeout for debugging test hangs michael@0: /*serv.timeoutCallback = function(tname) { michael@0: if (tname === 'testIpv4') michael@0: do_throw('testIpv4 never completed a connection to TestServ'); michael@0: }; michael@0: do_timeout(connectTimeout, function(){ serv.timeoutCallback('testIpv4'); });*/ michael@0: michael@0: testDataStore.transport = sts.createTransport(null, 0, '127.0.0.1', serv.port, null); michael@0: /* michael@0: * Need to hold |output| so that the output stream doesn't close itself and michael@0: * the associated connection. michael@0: */ michael@0: testDataStore.output = testDataStore.transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0); michael@0: michael@0: /* NEXT: michael@0: * openOutputStream -> onSocketAccepted -> acceptedCallback -> run_next_test michael@0: * OR (if the above timeout is uncommented) michael@0: * -> timeoutCallback -> do_throw michael@0: */ michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Running the tests. michael@0: */ michael@0: function run_test() { michael@0: sts = Cc["@mozilla.org/network/socket-transport-service;1"] michael@0: .getService(Ci.nsISocketTransportService); michael@0: serv = new TestServer(); michael@0: michael@0: do_register_cleanup(function(){ serv.stop(); }); michael@0: michael@0: add_test(testIpv4); michael@0: /* TODO: testIpv6 */ michael@0: /* TODO: testLocal */ michael@0: michael@0: run_next_test(); michael@0: }