Bug 965656 - wait for close of all data channels in all tests. r=jesup
authorNils Ohlmeier [:drno] <drno@ohlmeier.org>
Mon, 02 Jun 2014 20:10:00 +0200
changeset 205474 fca1951dff5c8cee2cd4d6ec1ebf697f93c88e70
parent 205473 7396db1badf558a89f96c0eff809fb2f63755263
child 205475 1fa787bf69b94566b27b821d1997484e42f2c93f
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs965656
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 965656 - wait for close of all data channels in all tests. r=jesup
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/templates.js
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -561,21 +561,24 @@ PeerConnectionTest.prototype.close = fun
       self.pcLocal.close();
     }
     if ((self.pcRemote) && (self.pcRemote.signalingState !== "closed")) {
       info("Closing pcRemote");
       self.pcRemote.onsignalingstatechange = signalingstatechangeRemoteClose;
       self.waitingForRemote = true;
       self.pcRemote.close();
     }
-    verifyClosed();
+    // give the signals handlers time to fire
+    setTimeout(verifyClosed, 1000);
   }
 
   closeTimeout = setTimeout(function() {
-    ok(false, "Closing PeerConnections timed out!");
+    var closed = ((self.pcLocal && (self.pcLocal.signalingState === "closed")) &&
+      (self.pcRemote && (self.pcRemote.signalingState === "closed")));
+    ok(closed, "Closing PeerConnections timed out");
     // it is not a success, but the show must go on
     onSuccess();
   }, 60000);
 
   closeEverything();
 };
 
 /**
@@ -789,63 +792,183 @@ function DataChannelTest(options) {
 }
 
 DataChannelTest.prototype = Object.create(PeerConnectionTest.prototype, {
   close : {
     /**
      * Close the open data channels, followed by the underlying peer connection
      *
      * @param {Function} onSuccess
-     *        Callback to execute when the connection has been closed
+     *        Callback to execute when all connections have been closed
      */
     value : function DCT_close(onSuccess) {
       var self = this;
+      var pendingDcClose = []
+      var closeTimeout = null;
 
-      function _closeChannels() {
-        var length = self.pcLocal.dataChannels.length;
+      info("DataChannelTest.close() called");
 
-        if (length > 0) {
-          self.closeDataChannel(length - 1, function () {
-            _closeChannels();
-          });
+      function _closePeerConnection() {
+        info("DataChannelTest closing PeerConnection");
+        PeerConnectionTest.prototype.close.call(self, onSuccess);
+      }
+
+      function _closePeerConnectionCallback(index) {
+        info("_closePeerConnection called with index " + index);
+        var pos = pendingDcClose.indexOf(index);
+        if (pos != -1) {
+          pendingDcClose.splice(pos, 1);
         }
         else {
-          PeerConnectionTest.prototype.close.call(self, onSuccess);
+          info("_closePeerConnection index " + index + " is missing from pendingDcClose: " + pendingDcClose);
+        }
+        if (pendingDcClose.length === 0) {
+          clearTimeout(closeTimeout);
+          _closePeerConnection();
         }
       }
 
-      _closeChannels();
+      var myDataChannels = null;
+      if (self.pcLocal) {
+        myDataChannels = self.pcLocal.dataChannels;
+      }
+      else if (self.pcRemote) {
+        myDataChannels = self.pcRemote.dataChannels;
+      }
+      var length = myDataChannels.length;
+      for (var i = 0; i < length; i++) {
+        var dataChannel = myDataChannels[i];
+        if (dataChannel.readyState !== "closed") {
+          pendingDcClose.push(i);
+          self.closeDataChannels(i, _closePeerConnectionCallback);
+        }
+      }
+      if (pendingDcClose.length === 0) {
+        _closePeerConnection();
+      }
+      else {
+        closeTimeout = setTimeout(function() {
+          ok(false, "Failed to properly close data channels: " +
+            pendingDcClose);
+          _closePeerConnection();
+        }, 60000);
+      }
     }
   },
 
-  closeDataChannel : {
+  closeDataChannels : {
     /**
-     * Close the specified data channel
+     * Close the specified data channels
      *
      * @param {Number} index
-     *        Index of the data channel to close on both sides
+     *        Index of the data channels to close on both sides
      * @param {Function} onSuccess
-     *        Callback to execute when the data channel has been closed
+     *        Callback to execute when the data channels has been closed
      */
-    value : function DCT_closeDataChannel(index, onSuccess) {
-      var localChannel = this.pcLocal.dataChannels[index];
-      var remoteChannel = this.pcRemote.dataChannels[index];
+    value : function DCT_closeDataChannels(index, onSuccess) {
+      info("_closeDataChannels called with index: " + index);
+      var localChannel = null;
+      if (this.pcLocal) {
+        localChannel = this.pcLocal.dataChannels[index];
+      }
+      var remoteChannel = null;
+      if (this.pcRemote) {
+        remoteChannel = this.pcRemote.dataChannels[index];
+      }
 
       var self = this;
+      var wait = false;
+      var pollingMode = false;
+      var everythingClosed = false;
+      var verifyInterval = null;
+      var remoteCloseTimer = null;
 
-      // Register handler for remote channel, cause we have to wait until
-      // the current close operation has been finished.
-      remoteChannel.onclose = function () {
-        self.pcRemote.dataChannels.splice(index, 1);
+      function _allChannelsAreClosed() {
+        var ret = null;
+        if (localChannel) {
+          ret = (localChannel.readyState === "closed");
+        }
+        if (remoteChannel) {
+          if (ret !== null) {
+            ret = (ret && (remoteChannel.readyState === "closed"));
+          }
+          else {
+            ret = (remoteChannel.readyState === "closed");
+          }
+        }
+        return ret;
+      }
+
+      function verifyClosedChannels() {
+        if (everythingClosed) {
+          // safety protection against events firing late
+          return;
+        }
+        if (_allChannelsAreClosed) {
+          ok(true, "DataChannel(s) have reached 'closed' state for data channel " + index);
+          if (remoteCloseTimer !== null) {
+            clearTimeout(remoteCloseTimer);
+          }
+          if (verifyInterval !== null) {
+            clearInterval(verifyInterval);
+          }
+          everythingClosed = true;
+          onSuccess(index);
+        }
+        else {
+          info("Still waiting for DataChannel closure");
+        }
+      }
 
-        onSuccess(remoteChannel);
-      };
+      if ((localChannel) && (localChannel.readyState !== "closed")) {
+        // in case of steeplechase there is no far end, so we can only poll
+        if (remoteChannel) {
+          remoteChannel.onclose = function () {
+            is(remoteChannel.readyState, "closed", "remoteChannel is in state 'closed'");
+            verifyClosedChannels();
+          };
+        }
+        else {
+          pollingMode = true;
+          verifyInterval = setInterval(verifyClosedChannels, 1000);
+        }
+
+        localChannel.close();
+        wait = true;
+      }
+      if ((remoteChannel) && (remoteChannel.readyState !== "closed")) {
+        if (localChannel) {
+          localChannel.onclose = function () {
+            is(localChannel.readyState, "closed", "localChannel is in state 'closed'");
+            verifyClosedChannels();
+          };
 
-      localChannel.close();
-      this.pcLocal.dataChannels.splice(index, 1);
+          // Apparently we are running a local test which has both ends of the
+          // data channel locally available, so by default lets wait for the
+          // remoteChannel.onclose handler from above to confirm closure on both
+          // ends.
+          remoteCloseTimer = setTimeout(function() {
+            todo(false, "localChannel.close() did not resulted in close signal on remote side");
+            remoteChannel.close();
+            verifyClosedChannels();
+          }, 30000);
+        }
+        else {
+          pollingMode = true;
+          verifyTimer = setInterval(verifyClosedChannels, 1000);
+
+          remoteChannel.close();
+        }
+
+        wait = true;
+      }
+
+      if (!wait) {
+        onSuccess(index);
+      }
     }
   },
 
   createDataChannel : {
     /**
      * Create a data channel
      *
      * @param {Dict} options
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -560,34 +560,10 @@ var commandsDataChannel = [
 
       test.send(message, function (channel, data) {
         is(channels.indexOf(channel), channels.length - 1, "Last channel used");
         is(data, message, "Received message has the correct content.");
 
         test.next();
       });
     }
-  ],
-  [
-    'CLOSE_LAST_OPENED_DATA_CHANNEL2',
-    function (test) {
-      var channels = test.pcRemote.dataChannels;
-
-      test.closeDataChannel(channels.length - 1, function (channel) {
-        is(channel.readyState, "closed", "Channel is in state: 'closed'");
-
-        test.next();
-      });
-    }
-  ],
-  [
-    'CLOSE_LAST_OPENED_DATA_CHANNEL',
-    function (test) {
-      var channels = test.pcRemote.dataChannels;
-
-      test.closeDataChannel(channels.length - 1, function (channel) {
-        is(channel.readyState, "closed", "Channel is in state: 'closed'");
-
-        test.next();
-      });
-    }
   ]
 ];