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