dom/network/tests/unit/test_tcpsocket.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /**
     2  * Test TCPSocket.js by creating an XPCOM-style server socket, then sending
     3  * data in both directions and making sure each side receives their data
     4  * correctly and with the proper events.
     5  *
     6  * This test is derived from netwerk/test/unit/test_socks.js, except we don't
     7  * involve a subprocess.
     8  *
     9  * Future work:
    10  * - SSL.  see https://bugzilla.mozilla.org/show_bug.cgi?id=466524
    11  *             https://bugzilla.mozilla.org/show_bug.cgi?id=662180
    12  *   Alternatively, mochitests could be used.
    13  * - Testing overflow logic.
    14  *
    15  **/
    17 const Cc = Components.classes;
    18 const Ci = Components.interfaces;
    19 const Cr = Components.results;
    20 const Cu = Components.utils;
    21 const CC = Components.Constructor;
    23 /**
    24  *
    25  * Constants
    26  *
    27  */
    29 // Some binary data to send.
    30 const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
    31       DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length),
    32       TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER),
    33       HELLO_WORLD = "hlo wrld. ",
    34       BIG_ARRAY = new Array(65539),
    35       BIG_ARRAY_2 = new Array(65539);
    37 TYPED_DATA_ARRAY.set(DATA_ARRAY, 0);
    39 for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
    40   BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
    41   BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
    42 }
    44 const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length),
    45       BIG_ARRAY_BUFFER_2 = new ArrayBuffer(BIG_ARRAY_2.length);
    46 const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER),
    47       BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_BUFFER_2);
    48 BIG_TYPED_ARRAY.set(BIG_ARRAY);
    49 BIG_TYPED_ARRAY_2.set(BIG_ARRAY_2);
    51 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
    52                         "nsIServerSocket",
    53                         "init"),
    54       InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
    55                            "nsIInputStreamPump",
    56                            "init"),
    57       BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
    58                              "nsIBinaryInputStream",
    59                              "setInputStream"),
    60       BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
    61                               "nsIBinaryOutputStream",
    62                               "setOutputStream"),
    63       TCPSocket = new (CC("@mozilla.org/tcp-socket;1",
    64                      "nsIDOMTCPSocket"))();
    66 const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
    67                   .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
    69 Cu.import("resource://gre/modules/Services.jsm");
    71 /**
    72  *
    73  * Helper functions
    74  *
    75  */
    77 function get_platform() {
    78   var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
    79                               .getService(Components.interfaces.nsIXULRuntime);
    80   return xulRuntime.OS;
    81 }
    83 function is_content() {
    84   return this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
    85                             .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
    86 }
    88 /**
    89  * Spin up a listening socket and associate at most one live, accepted socket
    90  * with ourselves.
    91  */
    92 function TestServer() {
    93   this.listener = ServerSocket(-1, true, -1);
    94   do_print('server: listening on', this.listener.port);
    95   this.listener.asyncListen(this);
    97   this.binaryInput = null;
    98   this.input = null;
    99   this.binaryOutput = null;
   100   this.output = null;
   102   this.onconnect = null;
   103   this.ondata = null;
   104   this.onclose = null;
   105 }
   107 TestServer.prototype = {
   108   onSocketAccepted: function(socket, trans) {
   109     if (this.input)
   110       do_throw("More than one live connection!?");
   112     do_print('server: got client connection');
   113     this.input = trans.openInputStream(0, 0, 0);
   114     this.binaryInput = new BinaryInputStream(this.input);
   115     this.output = trans.openOutputStream(0, 0, 0);
   116     this.binaryOutput = new BinaryOutputStream(this.output);
   118     new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null);
   120     if (this.onconnect)
   121       this.onconnect();
   122     else
   123       do_throw("Received unexpected connection!");
   124   },
   126   onStopListening: function(socket) {
   127   },
   129   onDataAvailable: function(request, context, inputStream, offset, count) {
   130     var readData = this.binaryInput.readByteArray(count);
   131     if (this.ondata) {
   132       try {
   133         this.ondata(readData);
   134       } catch(ex) {
   135         // re-throw if this is from do_throw
   136         if (ex === Cr.NS_ERROR_ABORT)
   137           throw ex;
   138         // log if there was a test problem
   139         do_print('Caught exception: ' + ex + '\n' + ex.stack);
   140         do_throw('test is broken; bad ondata handler; see above');
   141       }
   142     } else {
   143       do_throw('Received ' + count + ' bytes of unexpected data!');
   144     }
   145   },
   147   onStartRequest: function(request, context) {
   148   },
   150   onStopRequest: function(request, context, status) {
   151     if (this.onclose)
   152       this.onclose();
   153     else
   154       do_throw("Received unexpected close!");
   155   },
   157   close: function() {
   158     this.binaryInput.close();
   159     this.binaryOutput.close();
   160   },
   162   /**
   163    * Forget about the socket we knew about before.
   164    */
   165   reset: function() {
   166     this.binaryInput = null;
   167     this.input = null;
   168     this.binaryOutput = null;
   169     this.output = null;
   170   },
   171 };
   173 function makeSuccessCase(name) {
   174   return function() {
   175     do_print('got expected: ' + name);
   176     run_next_test();
   177   };
   178 }
   180 function makeJointSuccess(names) {
   181   let funcs = {}, successCount = 0;
   182   names.forEach(function(name) {
   183     funcs[name] = function() {
   184       do_print('got expected: ' + name);
   185       if (++successCount === names.length)
   186         run_next_test();
   187     };
   188   });
   189   return funcs;
   190 }
   192 function makeFailureCase(name) {
   193   return function() {
   194     let argstr;
   195     if (arguments.length) {
   196       argstr = '(args: ' +
   197         Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')';
   198     }
   199     else {
   200       argstr = '(no arguments)';
   201     }
   202     do_throw('got unexpected: ' + name + ' ' + argstr);
   203   };
   204 }
   206 function makeExpectData(name, expectedData, fromEvent, callback) {
   207   let dataBuffer = fromEvent ? null : [], done = false;
   208   let dataBufferView = null;
   209   return function(receivedData) {
   210     if (receivedData.data) {
   211       receivedData = receivedData.data;
   212     }
   213     let recvLength = receivedData.byteLength !== undefined ?
   214         receivedData.byteLength : receivedData.length;
   216     if (fromEvent) {
   217       if (dataBuffer) {
   218         let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength);
   219         let newBufferView = new Uint8Array(newBuffer);
   220         newBufferView.set(dataBufferView, 0);
   221         newBufferView.set(receivedData, dataBuffer.byteLength);
   222         dataBuffer = newBuffer;
   223         dataBufferView = newBufferView;
   224       }
   225       else {
   226         dataBuffer = receivedData;
   227         dataBufferView = new Uint8Array(dataBuffer);
   228       }
   229     }
   230     else {
   231       dataBuffer = dataBuffer.concat(receivedData);
   232     }
   233     do_print(name + ' received ' + recvLength + ' bytes');
   235     if (done)
   236       do_throw(name + ' Received data event when already done!');
   238     let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer;
   239     if (dataView.length >= expectedData.length) {
   240       // check the bytes are equivalent
   241       for (let i = 0; i < expectedData.length; i++) {
   242         if (dataView[i] !== expectedData[i]) {
   243           do_throw(name + ' Received mismatched character at position ' + i);
   244         }
   245       }
   246       if (dataView.length > expectedData.length)
   247         do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' +
   248                  expectedData.length + ' bytes.');
   250       done = true;
   251       if (callback) {
   252         callback();
   253       } else {
   254         run_next_test();
   255       }
   256     }
   257   };
   258 }
   260 var server = null, sock = null, failure_drain = null;
   262 /**
   263  *
   264  * Test functions
   265  *
   266  */
   268 /**
   269  * Connect the socket to the server. This test is added as the first
   270  * test, and is also added after every test which results in the socket
   271  * being closed.
   272  */
   274 function connectSock() {
   275   server.reset();
   276   var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']);
   278   sock = TCPSocket.open(
   279     '127.0.0.1', server.listener.port,
   280     { binaryType: 'arraybuffer' });
   282   sock.onopen = yayFuncs.clientopen;
   283   sock.ondrain = null;
   284   sock.ondata = makeFailureCase('data');
   285   sock.onerror = makeFailureCase('error');
   286   sock.onclose = makeFailureCase('close');
   288   server.onconnect = yayFuncs.serveropen;
   289   server.ondata = makeFailureCase('serverdata');
   290   server.onclose = makeFailureCase('serverclose');
   291 }
   293 /**
   294  * Test that sending a small amount of data works, and that buffering
   295  * does not take place for this small amount of data.
   296  */
   298 function sendData() {
   299   server.ondata = makeExpectData('serverdata', DATA_ARRAY);
   300   if (!sock.send(DATA_ARRAY_BUFFER)) {
   301     do_throw("send should not have buffered such a small amount of data");
   302   }
   303 }
   305 /**
   306  * Test that sending a large amount of data works, that buffering
   307  * takes place (send returns true), and that ondrain is called once
   308  * the data has been sent.
   309  */
   311 function sendBig() {
   312   var yays = makeJointSuccess(['serverdata', 'clientdrain']),
   313       amount = 0;
   315   server.ondata = function (data) {
   316     amount += data.length;
   317     if (amount === BIG_TYPED_ARRAY.length) {
   318       yays.serverdata();
   319     }
   320   };
   321   sock.ondrain = function(evt) {
   322     if (sock.bufferedAmount) {
   323       do_throw("sock.bufferedAmount was > 0 in ondrain");
   324     }
   325     yays.clientdrain(evt);
   326   }
   327   if (sock.send(BIG_ARRAY_BUFFER)) {
   328     do_throw("expected sock.send to return false on large buffer send");
   329   }
   330 }
   332 /**
   333  * Test that data sent from the server correctly fires the ondata
   334  * callback on the client side.
   335  */
   337 function receiveData() {
   338   server.ondata = makeFailureCase('serverdata');
   339   sock.ondata = makeExpectData('data', DATA_ARRAY, true);
   341   server.binaryOutput.writeByteArray(DATA_ARRAY, DATA_ARRAY.length);
   342 }
   344 /**
   345  * Test that when the server closes the connection, the onclose callback
   346  * is fired on the client side.
   347  */
   349 function serverCloses() {
   350   // we don't really care about the server's close event, but we do want to
   351   // make sure it happened for sequencing purposes.
   352   var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
   353   sock.ondata = makeFailureCase('data');
   354   sock.onclose = yayFuncs.clientclose;
   355   server.onclose = yayFuncs.serverclose;
   357   server.close();
   358 }
   360 /**
   361  * Test that when the client closes the connection, the onclose callback
   362  * is fired on the server side.
   363  */
   365 function clientCloses() {
   366   // we want to make sure the server heard the close and also that the client's
   367   // onclose event fired for consistency.
   368   var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
   369   server.onclose = yayFuncs.serverclose;
   370   sock.onclose = yayFuncs.clientclose;
   372   sock.close();
   373 }
   375 /**
   376  * Send a large amount of data and immediately call close
   377  */
   379 function bufferedClose() {
   380   var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
   381   server.ondata = makeExpectData(
   382     "ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
   383   server.onclose = yays.serverclose;
   384   sock.onclose = yays.clientclose;
   385   sock.send(BIG_ARRAY_BUFFER);
   386   sock.close();
   387 }
   389 /**
   390  * Connect to a port we know is not listening so an error is assured,
   391  * and make sure that onerror and onclose are fired on the client side.
   392  */
   394 function badConnect() {
   395   // There's probably nothing listening on tcp port 2.
   396   sock = TCPSocket.open('127.0.0.1', 2);
   398   sock.onopen = makeFailureCase('open');
   399   sock.ondata = makeFailureCase('data');
   401   let success = makeSuccessCase('error');
   402   let gotError = false;
   403   sock.onerror = function(event) {
   404     do_check_eq(event.data.name, 'ConnectionRefusedError');
   405     gotError = true;
   406   };
   407   sock.onclose = function() {
   408     if (!gotError)
   409       do_throw('got close without error!');
   410     else
   411       success();
   412   };
   413 }
   415 /**
   416  * Test that calling send with enough data to buffer causes ondrain to
   417  * be invoked once the data has been sent, and then test that calling send
   418  * and buffering again causes ondrain to be fired again.
   419  */
   421 function drainTwice() {
   422   let yays = makeJointSuccess(
   423     ['ondrain', 'ondrain2',
   424     'ondata', 'ondata2',
   425     'serverclose', 'clientclose']);
   426   let ondrainCalled = false,
   427       ondataCalled = false;
   429   function maybeSendNextData() {
   430     if (!ondrainCalled || !ondataCalled) {
   431       // make sure server got data and client got ondrain.
   432       return;
   433     }
   435     server.ondata = makeExpectData(
   436       "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
   438     sock.ondrain = yays.ondrain2;
   440     if (sock.send(BIG_ARRAY_BUFFER_2)) {
   441       do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
   442     }
   444     sock.close();
   445   }
   447   function clientOndrain() {
   448     yays.ondrain();
   449     ondrainCalled = true;
   450     maybeSendNextData();
   451   }
   453   function serverSideCallback() {
   454     yays.ondata();
   455     ondataCalled = true;
   456     maybeSendNextData();
   457   }
   459   server.onclose = yays.serverclose;
   460   server.ondata = makeExpectData(
   461     "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
   463   sock.onclose = yays.clientclose;
   464   sock.ondrain = clientOndrain;
   466   if (sock.send(BIG_ARRAY_BUFFER)) {
   467     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   468   }
   469 }
   471 function cleanup() {
   472   do_print("Cleaning up");
   473   sock.close();
   474   if (!gInChild)
   475     Services.prefs.clearUserPref('dom.mozTCPSocket.enabled');
   476   run_next_test();
   477 }
   479 /**
   480  * Test that calling send with enough data to buffer twice in a row without
   481  * waiting for ondrain still results in ondrain being invoked at least once.
   482  */
   484 function bufferTwice() {
   485   let yays = makeJointSuccess(
   486     ['ondata', 'ondrain', 'serverclose', 'clientclose']);
   488   let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2));
   489   server.ondata = makeExpectData(
   490     "ondata", double_array, false, yays.ondata);
   492   server.onclose = yays.serverclose;
   493   sock.onclose = yays.clientclose;
   495   sock.ondrain = function () {
   496     sock.close();
   497     yays.ondrain();
   498   }
   500   if (sock.send(BIG_ARRAY_BUFFER)) {
   501     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   502   }
   503   if (sock.send(BIG_ARRAY_BUFFER_2)) {
   504     throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
   505   }
   506 }
   508 // Test child behavior when child thinks it's buffering but parent doesn't
   509 // buffer.
   510 // 1. set bufferedAmount of content socket to a value that will make next
   511 //    send() call return false.
   512 // 2. send a small data to make send() return false, but it won't make
   513 //    parent buffer.
   514 // 3. we should get a ondrain.
   515 function childbuffered() {
   516   let yays = makeJointSuccess(['ondrain', 'serverdata',
   517                                'clientclose', 'serverclose']);
   518   sock.ondrain = function() {
   519     yays.ondrain();
   520     sock.close();
   521   };
   523   server.ondata = makeExpectData(
   524     'ondata', DATA_ARRAY, false, yays.serverdata);
   526   let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
   527   internalSocket.updateBufferedAmount(65535, // almost reach buffering threshold
   528                                       0);
   529   if (sock.send(DATA_ARRAY_BUFFER)) {
   530     do_throw("expected sock.send to return false.");
   531   }
   533   sock.onclose = yays.clientclose;
   534   server.onclose = yays.serverclose;
   535 }
   537 // Test child's behavior when send() of child return true but parent buffers
   538 // data.
   539 // 1. send BIG_ARRAY to make parent buffer. This would make child wait for
   540 //    drain as well.
   541 // 2. set child's bufferedAmount to zero, so child will no longer wait for
   542 //    drain but parent will dispatch a drain event.
   543 // 3. wait for 1 second, to make sure there's no ondrain event dispatched in
   544 //    child.
   545 function childnotbuffered() {
   546   let yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
   547   server.ondata = makeExpectData('ondata', BIG_ARRAY, false, yays.serverdata);
   548   if (sock.send(BIG_ARRAY_BUFFER)) {
   549     do_throw("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   550   }
   551   let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
   552   internalSocket.updateBufferedAmount(0, // setting zero will clear waitForDrain in sock.
   553                                       1);
   555   // shouldn't get ondrain, even after parent have cleared its buffer.
   556   sock.ondrain = makeFailureCase('drain');
   557   sock.onclose = yays.clientclose;
   558   server.onclose = yays.serverclose;
   559   do_timeout(1000, function() {
   560     sock.close();
   561   });
   562 };
   564 // - connect, data and events work both ways
   565 add_test(connectSock);
   566 add_test(sendData);
   567 add_test(sendBig);
   568 add_test(receiveData);
   569 // - server closes on us
   570 add_test(serverCloses);
   572 // - connect, we close on the server
   573 add_test(connectSock);
   574 add_test(clientCloses);
   576 // - connect, buffer, close
   577 add_test(connectSock);
   578 add_test(bufferedClose);
   580 if (get_platform() !== "Darwin") {
   581   // This test intermittently fails way too often on OS X, for unknown reasons.
   582   // Please, diagnose and fix it if you can.
   583   // - get an error on an attempt to connect to a non-listening port
   584   add_test(badConnect);
   585 }
   587 // send a buffer, get a drain, send a buffer, get a drain
   588 add_test(connectSock);
   589 add_test(drainTwice);
   591 // send a buffer, get a drain, send a buffer, get a drain
   592 add_test(connectSock);
   593 add_test(bufferTwice);
   595 if (is_content()) {
   596   add_test(connectSock);
   597   add_test(childnotbuffered);
   599   add_test(connectSock);
   600   add_test(childbuffered);
   601 }
   603 // clean up
   604 add_test(cleanup);
   606 function run_test() {
   607   if (!gInChild)
   608     Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
   610   server = new TestServer();
   612   run_next_test();
   613 }

mercurial