Bug 1231981 - Part 3: Set up TURN server for webrtc mochitests, when configured to. r=drno draft
authorByron Campen [:bwc] <docfaraday@gmail.com>
Fri, 04 Mar 2016 15:45:57 -0600
changeset 357878 3e8d6e0622d71925284ad164f90f01301d498faa
parent 357877 34f4b174e630e21c429c173adfc44b4fbf54cd43
child 357879 941dfc1e9cd8cd8b79ba2dc637f29cc50b208934
push id16877
push userbcampen@mozilla.com
push dateFri, 29 Apr 2016 19:01:27 +0000
reviewersdrno
bugs1231981
milestone49.0a1
Bug 1231981 - Part 3: Set up TURN server for webrtc mochitests, when configured to. r=drno MozReview-Commit-ID: CVbAYPrwpuB
dom/media/tests/mochitest/head.js
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/templates.js
media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
testing/mochitest/runtests.py
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -332,19 +332,22 @@ function run_test(is_initiator,timeout) 
   s.src = "/test.js";
   s.onload = () => setTestOptions(options);
   document.head.appendChild(s);
 }
 
 function runTestWhenReady(testFunc) {
   setupEnvironment();
   return testConfigured.then(options => testFunc(options))
-    .catch(e => ok(false, 'Error executing test: ' + e +
+    .catch(e => {
+      ok(false, 'Error executing test: ' + e +
         ((typeof e.stack === 'string') ?
-        (' ' + e.stack.split('\n').join(' ... ')) : '')));
+        (' ' + e.stack.split('\n').join(' ... ')) : ''));
+      SimpleTest.finish();
+    });
 }
 
 
 /**
  * Checks that the media stream tracks have the expected amount of tracks
  * with the correct kind and id based on the type and constraints given.
  *
  * @param {Object} constraints specifies whether the stream should have
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -59,17 +59,23 @@ function PeerConnectionTest(options) {
   options.is_local = "is_local" in options ? options.is_local : true;
   options.is_remote = "is_remote" in options ? options.is_remote : true;
 
   options.h264 = "h264" in options ? options.h264 : false;
   options.bundle = "bundle" in options ? options.bundle : true;
   options.rtcpmux = "rtcpmux" in options ? options.rtcpmux : true;
   options.opus = "opus" in options ? options.opus : true;
 
-  if (typeof turnServers !== "undefined") {
+  if (iceServersArray.length) {
+    options.config_remote = options.config_remote || {}
+    options.config_local = options.config_local || {}
+    options.config_remote.iceServers = iceServersArray;
+    options.config_local.iceServers = iceServersArray;
+  }
+  else if (typeof turnServers !== "undefined") {
     if ((!options.turn_disabled_local) && (turnServers.local)) {
       if (!options.hasOwnProperty("config_local")) {
         options.config_local = {};
       }
       if (!options.config_local.hasOwnProperty("iceServers")) {
         options.config_local.iceServers = turnServers.local.iceServers;
       }
     }
@@ -1674,17 +1680,17 @@ PeerConnectionWrapper.prototype = {
 
   /**
    * Compares the Ice server configured for this PeerConnectionWrapper
    * with the ICE candidates received in the RTCP stats.
    *
    * @param {object} stats
    *        The stats to be verified for relayed vs. direct connection.
    */
-  checkStatsIceConnectionType : function(stats) {
+  checkStatsIceConnectionType : function(stats, expectedLocalCandidateType) {
     let lId;
     let rId;
     for (let stat of stats.values()) {
       if (stat.type == "candidatepair" && stat.selected) {
         lId = stat.localCandidateId;
         rId = stat.remoteCandidateId;
         break;
       }
@@ -1693,31 +1699,35 @@ PeerConnectionWrapper.prototype = {
     isnot(rId, undefined, "Got remote candidate ID " + rId + " for selected pair");
     let lCand = stats.get(lId);
     let rCand = stats.get(rId);
     if (!lCand || !rCand) {
       ok(false,
          "failed to find candidatepair IDs or stats for local: "+ lId +" remote: "+ rId);
       return;
     }
+
     info("checkStatsIceConnectionType verifying: local=" +
          JSON.stringify(lCand) + " remote=" + JSON.stringify(rCand));
-    if ((this.configuration) && (typeof this.configuration.iceServers !== 'undefined')) {
-      info("Ice Server configured");
-      // Note: the IP comparising is a workaround for bug 1097333
-      //       And this will fail if a TURN server address is a DNS name!
-      var serverIp = this.configuration.iceServers[0].url.split(':')[1];
-      ok(lCand.candidateType == "relayed" || rCand.candidateType == "relayed" ||
-         lCand.ipAddress === serverIp || rCand.ipAddress === serverIp,
-         "One peer uses a relay");
-    } else {
-      info("P2P configured");
-      ok(lCand.candidateType != "relayed" && rCand.candidateType != "relayed",
-         "Pure peer to peer call without a relay");
+    expectedLocalCandidateType = expectedLocalCandidateType || "host";
+    var candidateType = lCand.candidateType;
+    if ((lCand.mozLocalTransport === "tcp") && (candidateType === "relayed")) {
+      candidateType = "relayed-tcp";
     }
+
+    if ((expectedLocalCandidateType === "serverreflexive") &&
+        (candidateType === "peerreflexive")) {
+      // Be forgiving of prflx when expecting srflx, since that can happen due
+      // to timing.
+      candidateType = "serverreflexive";
+    }
+
+    is(candidateType,
+       expectedLocalCandidateType,
+       "Local candidate type is what we expected for selected pair");
   },
 
   /**
    * Compares amount of established ICE connection according to ICE candidate
    * pairs in the stats reporting with the expected amount of connection based
    * on the constraints.
    *
    * @param {object} stats
@@ -1840,16 +1850,65 @@ var scriptsReady = Promise.all([
   document.head.appendChild(el);
   return new Promise(r => { el.onload = r; el.onerror = r; });
 }));
 
 function createHTML(options) {
   return scriptsReady.then(() => realCreateHTML(options));
 }
 
-function runNetworkTest(testFunction) {
+var iceServerWebsocket;
+var iceServersArray = [];
+
+var setupIceServerConfig = useIceServer => {
+  // We disable ICE support for HTTP proxy when using a TURN server, because
+  // mochitest uses a fake HTTP proxy to serve content, which will eat our STUN
+  // packets for TURN TCP.
+  var enableHttpProxy = enable => new Promise(resolve => {
+    SpecialPowers.pushPrefEnv(
+        {'set': [['media.peerconnection.disable_http_proxy', !enable]]},
+        resolve);
+  });
+
+  var spawnIceServer = () => new Promise( (resolve, reject) => {
+    iceServerWebsocket = new WebSocket("ws://localhost:8191/");
+    iceServerWebsocket.onopen = (event) => {
+      info("websocket/process bridge open, starting ICE Server...");
+      iceServerWebsocket.send("iceserver");
+    }
+
+    iceServerWebsocket.onmessage = event => {
+      // The first message will contain the iceServers configuration, subsequent
+      // messages are just logging.
+      info("ICE Server: " + event.data);
+      resolve(event.data);
+    }
+
+    iceServerWebsocket.onerror = () => {
+      reject("ICE Server error: Is the ICE server websocket up?");
+    }
+
+    iceServerWebsocket.onclose = () => {
+      info("ICE Server websocket closed");
+      reject("ICE Server gone before getting configuration");
+    }
+  });
+
+  if (!useIceServer) {
+    info("Skipping ICE Server for this test");
+    return enableHttpProxy(true);
+  }
+
+  return enableHttpProxy(false)
+    .then(spawnIceServer)
+    .then(iceServersStr => { iceServersArray = JSON.parse(iceServersStr); });
+};
+
+function runNetworkTest(testFunction, fixtureOptions) {
+  fixtureOptions = fixtureOptions || {}
   return scriptsReady.then(() =>
     runTestWhenReady(options =>
       startNetworkAndTest()
+        .then(() => setupIceServerConfig(fixtureOptions.useIceServer))
         .then(() => testFunction(options))
     )
   );
 }
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -436,23 +436,25 @@ var commandsPeerConnectionOfferAnswer = 
   function PC_REMOTE_CHECK_STATS(test) {
     return test.pcRemote.getStats().then(stats => {
       test.pcRemote.checkStats(stats, test.testOptions.steeplechase);
     });
   },
 
   function PC_LOCAL_CHECK_ICE_CONNECTION_TYPE(test) {
     return test.pcLocal.getStats().then(stats => {
-      test.pcLocal.checkStatsIceConnectionType(stats);
+      test.pcLocal.checkStatsIceConnectionType(stats,
+          test.testOptions.expectedLocalCandidateType);
     });
   },
 
   function PC_REMOTE_CHECK_ICE_CONNECTION_TYPE(test) {
     return test.pcRemote.getStats().then(stats => {
-      test.pcRemote.checkStatsIceConnectionType(stats);
+      test.pcRemote.checkStatsIceConnectionType(stats,
+          test.testOptions.expectedRemoteCandidateType);
     });
   },
 
   function PC_LOCAL_CHECK_ICE_CONNECTIONS(test) {
     return test.pcLocal.getStats().then(stats => {
       test.pcLocal.checkStatsIceConnections(stats,
                                             test._offer_constraints,
                                             test._offer_options,
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
@@ -210,18 +210,18 @@ int nr_ice_candidate_create(nr_ice_ctx *
       cand->u.relayed.turn_sock=osock;
 
 
     /* Add the candidate to the isock list*/
     TAILQ_INSERT_TAIL(&isock->candidates,cand,entry_sock);
 
     nr_ice_candidate_compute_codeword(cand);
 
-    r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): created candidate %s with type %s",
-      ctx->label,cand->label,nr_ctype_name(ctype));
+    r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): created candidate %s with type %s",
+      ctx->label,cand->codeword,cand->label,nr_ctype_name(ctype));
 
     *candp=cand;
 
     _status=0;
   abort:
     if (_status){
       r_log(LOG_ICE,LOG_ERR,"ICE(%s): Failed to create candidate of type %s", ctx->label,nr_ctype_name(ctype));
       nr_ice_candidate_destroy(&cand);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -512,16 +512,17 @@ void nr_ice_gather_finished_cb(NR_SOCKET
 
 
     assert(cb_arg);
     if (!cb_arg)
       return;
     ctx = cand->ctx;
 
     ctx->uninitialized_candidates--;
+    r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): initialized, %d remaining",ctx->label,cand->codeword,ctx->uninitialized_candidates);
 
     /* Avoid the need for yet another initialization function */
     if (cand->state == NR_ICE_CAND_STATE_INITIALIZING && cand->type == HOST)
       cand->state = NR_ICE_CAND_STATE_INITIALIZED;
 
     if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
       int was_pruned = 0;
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -238,20 +238,30 @@ PeerConnectionMedia::PeerConnectionMedia
       mDNSResolver(new NrIceResolver()),
       mUuidGen(MakeUnique<PCUuidGenerator>()),
       mMainThread(mParent->GetMainThread()),
       mSTSThread(mParent->GetSTSThread()),
       mProxyResolveCompleted(false),
       mIceRestartState(ICE_RESTART_NONE) {
 }
 
-nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
-                                   const std::vector<NrIceTurnServer>& turn_servers,
-                                   NrIceCtx::Policy policy)
+nsresult
+PeerConnectionMedia::InitProxy()
 {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  // Allow mochitests to disable this, since mochitest configures a fake proxy
+  // that serves up content.
+  bool disable = Preferences::GetBool("media.peerconnection.disable_http_proxy",
+                                      false);
+  if (disable) {
+    mProxyResolveCompleted = true;
+    return NS_OK;
+  }
+#endif
+
   nsresult rv;
   nsCOMPtr<nsIProtocolProxyService> pps =
     do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "%s: Failed to get proxy service: %d", __FUNCTION__, (int)rv);
     return NS_ERROR_FAILURE;
   }
 
@@ -299,16 +309,26 @@ nsresult PeerConnectionMedia::Init(const
                          nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
                          nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
                          handler, getter_AddRefs(mProxyRequest));
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "%s: Failed to resolve protocol proxy: %d", __FUNCTION__, (int)rv);
     return NS_ERROR_FAILURE;
   }
 
+  return NS_OK;
+}
+
+nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
+                                   const std::vector<NrIceTurnServer>& turn_servers,
+                                   NrIceCtx::Policy policy)
+{
+  nsresult rv = InitProxy();
+  NS_ENSURE_SUCCESS(rv, rv);
+
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
 #else
   bool ice_tcp = false;
 #endif
   bool default_address_only = GetPrefDefaultAddressOnly();
 
   // TODO(ekr@rtfm.com): need some way to set not offerer later
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -479,16 +479,17 @@ class PeerConnectionMedia : public sigsl
   // This passes address, port, level of the default candidate.
   sigslot::signal5<const std::string&, uint16_t,
                    const std::string&, uint16_t, uint16_t>
       SignalUpdateDefaultCandidate;
   sigslot::signal1<uint16_t>
       SignalEndOfLocalCandidates;
 
  private:
+  nsresult InitProxy();
   class ProtocolProxyQueryHandler : public nsIProtocolProxyCallback {
    public:
     explicit ProtocolProxyQueryHandler(PeerConnectionMedia *pcm) :
       pcm_(pcm) {}
 
     NS_IMETHODIMP OnProxyAvailable(nsICancelable *request,
                                    nsIChannel *aChannel,
                                    nsIProxyInfo *proxyinfo,
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -895,20 +895,20 @@ class MochitestBase(object):
             try:
                 self.log.info('Stopping ssltunnel')
                 self.sslTunnel.stop()
             except Exception:
                 self.log.critical('Exception stopping ssltunnel')
 
         if self.websocketProcessBridge is not None:
             try:
-                self.log.info('Stopping websocketProcessBridge')
                 self.websocketProcessBridge.kill()
+                self.log.info('Stopping websocket/process bridge')
             except Exception:
-                self.log.critical('Exception stopping websocketProcessBridge')
+                self.log.critical('Exception stopping websocket/process bridge')
 
     def copyExtraFilesToProfile(self, options):
         "Copy extra files or dirs specified on the command line to the testing profile."
         for f in options.extraProfileFiles:
             abspath = self.getFullPath(f)
             if os.path.isfile(abspath):
                 shutil.copy2(abspath, options.profilePath)
             elif os.path.isdir(abspath):