Bug 1167443: fix verification of end-of-candidates in mochitests. r?mt,r?bwc,r?jib draft
authorNils Ohlmeier [:drno] <drno@ohlmeier.org>
Mon, 24 Aug 2015 14:49:41 -0700
changeset 288151 2d44a0951c70ebecb6bf8a2c77664fe071ae3c1f
parent 288070 fea87cbeaa6b64510dff835549ed906fe405d558
child 508742 869dec861f69e3e2d4e710b0159e08ec24448a13
push id4810
push userdrno@ohlmeier.org
push dateWed, 26 Aug 2015 23:09:37 +0000
reviewersmt, bwc, jib
bugs1167443
milestone43.0a1
Bug 1167443: fix verification of end-of-candidates in mochitests. r?mt,r?bwc,r?jib
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/sdpUtils.js
dom/media/tests/mochitest/templates.js
dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
dom/media/tests/mochitest/test_peerConnection_addDataChannelNoBundle.html
dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
dom/media/tests/mochitest/test_peerConnection_basicAudioRequireEOC.html
dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundle.html
dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html
dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoRtcpMux.html
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
   mediaStreamPlayback.js
   network.js
   nonTrickleIce.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
+  sdpUtils.js
 
 [test_dataChannel_basicAudio.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicAudioVideoNoBundle.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicAudioVideoCombined.html]
@@ -57,22 +58,28 @@ skip-if = toolkit == 'gonk' || buildapp 
 [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
 [test_getUserMedia_stopVideoStream.html]
 [test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
 [test_getUserMedia_peerIdentity.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
 [test_peerConnection_addIceCandidate.html]
 [test_peerConnection_basicAudio.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
+[test_peerConnection_basicAudioRequireEOC.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
 [test_peerConnection_basicAudioVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicAudioVideoNoBundle.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_basicAudioVideoNoRtcpMux.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicScreenshare.html]
 # no screenshare on b2g/android
 # frequent timeouts/crashes on e10s (bug 1048455)
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'android' || e10s # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_peerConnection_basicWindowshare.html]
 # no screenshare on b2g/android
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -23,29 +23,16 @@ const signalingStateTransitions = {
   "stable": ["have-local-offer", "have-remote-offer", "closed"],
   "have-local-offer": ["have-remote-pranswer", "stable", "closed", "have-local-offer"],
   "have-remote-pranswer": ["stable", "closed", "have-remote-pranswer"],
   "have-remote-offer": ["have-local-pranswer", "stable", "closed", "have-remote-offer"],
   "have-local-pranswer": ["stable", "closed", "have-local-pranswer"],
   "closed": []
 }
 
-// Also remove mode 0 if it's offered
-// Note, we don't bother removing the fmtp lines, which makes a good test
-// for some SDP parsing issues.
-function removeVP8(sdp) {
-  var updated_sdp = sdp.replace("a=rtpmap:120 VP8/90000\r\n","");
-  updated_sdp = updated_sdp.replace("RTP/SAVPF 120 126 97\r\n","RTP/SAVPF 126 97\r\n");
-  updated_sdp = updated_sdp.replace("RTP/SAVPF 120 126\r\n","RTP/SAVPF 126\r\n");
-  updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack\r\n","");
-  updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack pli\r\n","");
-  updated_sdp = updated_sdp.replace("a=rtcp-fb:120 ccm fir\r\n","");
-  return updated_sdp;
-}
-
 var makeDefaultCommands = () => {
   return [].concat(commandsPeerConnectionInitial,
                    commandsGetUserMedia,
                    commandsPeerConnectionOfferAnswer);
 };
 
 /**
  * This class handles tests for peer connections.
@@ -67,16 +54,20 @@ var makeDefaultCommands = () => {
  */
 function PeerConnectionTest(options) {
   // If no options are specified make it an empty object
   options = options || { };
   options.commands = options.commands || makeDefaultCommands();
   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;
+
   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;
       }
@@ -87,35 +78,31 @@ function PeerConnectionTest(options) {
       }
       if (!options.config_remote.hasOwnProperty("iceServers")) {
         options.config_remote.iceServers = turnServers.remote.iceServers;
       }
     }
   }
 
   if (options.is_local)
-    this.pcLocal = new PeerConnectionWrapper('pcLocal', options.config_local, options.h264);
+    this.pcLocal = new PeerConnectionWrapper('pcLocal', options.config_local);
   else
     this.pcLocal = null;
 
   if (options.is_remote)
-    this.pcRemote = new PeerConnectionWrapper('pcRemote', options.config_remote || options.config_local, options.h264);
+    this.pcRemote = new PeerConnectionWrapper('pcRemote', options.config_remote || options.config_local);
   else
     this.pcRemote = null;
 
-  this.steeplechase = this.pcLocal === null || this.pcRemote === null;
+  options.steeplechase = this.pcLocal === null || this.pcRemote === null;
 
   // Create command chain instance and assign default commands
   this.chain = new CommandChain(this, options.commands);
-  if (!options.is_local) {
-    this.chain.filterOut(/^PC_LOCAL/);
-  }
-  if (!options.is_remote) {
-    this.chain.filterOut(/^PC_REMOTE/);
-  }
+
+  this.testOptions = options;
 }
 
 /** TODO: consider removing this dependency on timeouts */
 function timerGuard(p, time, message) {
   return Promise.race([
     p,
     wait(time).then(() => {
       throw new Error('timeout after ' + (time / 1000) + 's: ' + message);
@@ -327,16 +314,24 @@ function(peer, desc, stateExpected) {
       }
     };
   });
 
   var stateChanged = peer.setLocalDescription(desc).then(() => {
     peer.setLocalDescDate = new Date();
   });
 
+  peer.endOfTrickleSdp = peer.endOfTrickleIce.then(() => {
+    if (this.testOptions.steeplechase) {
+      send_message({"type": "end_of_trickle_ice"});
+    }
+    return peer._pc.localDescription;
+  })
+  .catch(e => ok(false, "Sending EOC message failed: " + e));
+
   return Promise.all([eventFired, stateChanged]);
 };
 
 /**
  * Sets the media constraints for both peer connection instances.
  *
  * @param {object} constraintsLocal
  *        Media constrains for the local peer connection instance
@@ -395,16 +390,39 @@ function(peer, desc, stateExpected) {
 
   return Promise.all([eventFired, stateChanged]);
 };
 
 /**
  * Start running the tests as assigned to the command chain.
  */
 PeerConnectionTest.prototype.run = function() {
+  /* We have to modify the chain here to allow tests which modify the default
+   * test chain instantiating a PeerConnectionTest() */
+  if(this.testOptions.h264) {
+    this.chain.insertAfterEach(
+      'PC_LOCAL_CREATE_OFFER',
+      [PC_LOCAL_REMOVE_VP8_FROM_OFFER]);
+  }
+  if(!this.testOptions.bundle) {
+    this.chain.insertAfterEach(
+      'PC_LOCAL_CREATE_OFFER',
+      [PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER]);
+  }
+  if(!this.testOptions.rtcpmux) {
+    this.chain.insertAfterEach(
+      'PC_LOCAL_CREATE_OFFER',
+      [PC_LOCAL_REMOVE_RTCPMUX_FROM_OFFER]);
+  }
+  if (!this.testOptions.is_local) {
+    this.chain.filterOut(/^PC_LOCAL/);
+  }
+  if (!this.testOptions.is_remote) {
+    this.chain.filterOut(/^PC_REMOTE/);
+  }
   return this.chain.execute()
     .then(() => this.close())
     .then(() => {
       if (window.SimpleTest) {
         networkTestFinished();
       } else {
         finish();
       }
@@ -646,17 +664,17 @@ DataChannelWrapper.prototype = {
  * This class acts as a wrapper around a PeerConnection instance.
  *
  * @constructor
  * @param {string} label
  *        Description for the peer connection instance
  * @param {object} configuration
  *        Configuration for the peer connection instance
  */
-function PeerConnectionWrapper(label, configuration, h264) {
+function PeerConnectionWrapper(label, configuration) {
   this.configuration = configuration;
   if (configuration && configuration.label_suffix) {
     label = label + "_" + configuration.label_suffix;
   }
   this.label = label;
   this.whenCreated = Date.now();
 
   this.constraints = [ ];
@@ -675,17 +693,17 @@ function PeerConnectionWrapper(label, co
   this.expectedLocalTrackInfoById = {};
   this.expectedRemoteTrackInfoById = {};
   this.observedRemoteTrackInfoById = {};
 
   this.disableRtpCountChecking = false;
 
   this.iceCheckingRestartExpected = false;
 
-  this.h264 = typeof h264 !== "undefined" ? true : false;
+  this.offerAnswerCounter = 0;
 
   info("Creating " + this);
   this._pc = new mozRTCPeerConnection(this.configuration);
 
   /**
    * Setup callback handlers
    */
   // This allows test to register their own callbacks for ICE connection state changes
@@ -913,20 +931,16 @@ PeerConnectionWrapper.prototype = {
   /**
    * Creates an offer and automatically handles the failure case.
    */
   createOffer : function() {
     return this._pc.createOffer(this.offerOptions).then(offer => {
       info("Got offer: " + JSON.stringify(offer));
       // note: this might get updated through ICE gathering
       this._latest_offer = offer;
-      if (this.h264) {
-        isnot(offer.sdp.search("H264/90000"), -1, "H.264 should be present in the SDP offer");
-        offer.sdp = removeVP8(offer.sdp);
-      }
       return offer;
     });
   },
 
   /**
    * Creates an answer and automatically handles the failure case.
    */
   createAnswer : function() {
@@ -941,16 +955,17 @@ PeerConnectionWrapper.prototype = {
    * Sets the local description and automatically handles the failure case.
    *
    * @param {object} desc
    *        mozRTCSessionDescription for the local description request
    */
   setLocalDescription : function(desc) {
     this.observedNegotiationNeeded = undefined;
     return this._pc.setLocalDescription(desc).then(() => {
+      this.offerAnswerCounter += 1;
       info(this + ": Successfully set the local description");
     });
   },
 
   /**
    * Tries to set the local description and expect failure. Automatically
    * causes the test case to fail if the call succeeds.
    *
@@ -1210,55 +1225,37 @@ PeerConnectionWrapper.prototype = {
    */
   setupIceCandidateHandler : function(test, candidateHandler) {
     candidateHandler = candidateHandler || test.iceCandidateHandler.bind(test);
 
     var resolveEndOfTrickle;
     this.endOfTrickleIce = new Promise(r => resolveEndOfTrickle = r);
     this.holdIceCandidates = new Promise(r => this.releaseIceCandidates = r);
 
-    this.endOfTrickleIce.then(() => {
-      this._pc.onicecandidate = () =>
-        ok(false, this.label + " received ICE candidate after end of trickle");
-      var localSdp = this._pc.getLocalDescription();
-      ok(localSdp.includes("a=end-of-candidates"));
-      ok(localSdp.includes("a=rtcp:"));
-      ok(!localSdp.includes("c=IN IP4 0.0.0.0"));
-    });
-
     this._pc.onicecandidate = anEvent => {
       if (!anEvent.candidate) {
+        this._pc.onicecandidate = () =>
+          ok(false, this.label + " received ICE candidate after end of trickle");
         info(this.label + ": received end of trickle ICE event");
+        todo(this._pc.iceGatheringState === 'completed',
+           "ICE gathering state has reached completed");
         resolveEndOfTrickle(this.label);
         return;
       }
 
       info(this.label + ": iceCandidate = " + JSON.stringify(anEvent.candidate));
       ok(anEvent.candidate.candidate.length > 0, "ICE candidate contains candidate");
       // we don't support SDP MID's yet
       ok(anEvent.candidate.sdpMid.length === 0, "SDP MID has length zero");
       ok(typeof anEvent.candidate.sdpMLineIndex === 'number', "SDP MLine Index needs to exist");
       this._local_ice_candidates.push(anEvent.candidate);
       candidateHandler(this.label, anEvent.candidate);
     };
   },
 
-  /**
-   * Counts the amount of audio tracks in a given media constraint.
-   *
-   * @param constraints
-   *        The contraint to be examined.
-   */
-  countTracksInConstraint : function(type, constraints) {
-    if (!Array.isArray(constraints)) {
-      return 0;
-    }
-    return constraints.reduce((sum, c) => sum + (c[type] ? 1 : 0), 0);
-  },
-
   checkLocalMediaTracks : function() {
     var observed = {};
     info(this + " Checking local tracks " + JSON.stringify(this.expectedLocalTrackInfoById));
     this._pc.getSenders().forEach(sender => {
       this.checkTrackIsExpected(sender.track, this.expectedLocalTrackInfoById, observed);
     });
 
     Object.keys(this.expectedLocalTrackInfoById).forEach(
@@ -1297,76 +1294,16 @@ PeerConnectionWrapper.prototype = {
     };
 
     checkSdpForMsids(this.localDescription, this.expectedLocalTrackInfoById,
                      "local");
     checkSdpForMsids(this.remoteDescription, this.expectedRemoteTrackInfoById,
                      "remote");
   },
 
-  verifySdp: function(desc, expectedType, offerConstraintsList, offerOptions, isLocal) {
-    info("Examining this SessionDescription: " + JSON.stringify(desc));
-    info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
-    info("offerOptions: " + JSON.stringify(offerOptions));
-    ok(desc, "SessionDescription is not null");
-    is(desc.type, expectedType, "SessionDescription type is " + expectedType);
-    ok(desc.sdp.length > 10, "SessionDescription body length is plausible");
-    ok(desc.sdp.includes("a=ice-ufrag"), "ICE username is present in SDP");
-    ok(desc.sdp.includes("a=ice-pwd"), "ICE password is present in SDP");
-    ok(desc.sdp.includes("a=fingerprint"), "ICE fingerprint is present in SDP");
-    //TODO: update this for loopback support bug 1027350
-    ok(!desc.sdp.includes(LOOPBACK_ADDR), "loopback interface is absent from SDP");
-    var requiresTrickleIce = !desc.sdp.includes("a=candidate");
-    if (requiresTrickleIce) {
-      info("at least one ICE candidate is present in SDP");
-    } else {
-      info("No ICE candidate in SDP -> requiring trickle ICE");
-    }
-    if (isLocal) {
-      this.localRequiresTrickleIce = requiresTrickleIce;
-    } else {
-      this.remoteRequiresTrickleIce = requiresTrickleIce;
-    }
-
-    //TODO: how can we check for absence/presence of m=application?
-
-    var audioTracks =
-        this.countTracksInConstraint('audio', offerConstraintsList) ||
-        ((offerOptions && offerOptions.offerToReceiveAudio) ? 1 : 0);
-
-    info("expected audio tracks: " + audioTracks);
-    if (audioTracks == 0) {
-      ok(!desc.sdp.includes("m=audio"), "audio m-line is absent from SDP");
-    } else {
-      ok(desc.sdp.includes("m=audio"), "audio m-line is present in SDP");
-      ok(desc.sdp.includes("a=rtpmap:109 opus/48000/2"), "OPUS codec is present in SDP");
-      //TODO: ideally the rtcp-mux should be for the m=audio, and not just
-      //      anywhere in the SDP (JS SDP parser bug 1045429)
-      ok(desc.sdp.includes("a=rtcp-mux"), "RTCP Mux is offered in SDP");
-    }
-
-    var videoTracks =
-        this.countTracksInConstraint('video', offerConstraintsList) ||
-        ((offerOptions && offerOptions.offerToReceiveVideo) ? 1 : 0);
-
-    info("expected video tracks: " + videoTracks);
-    if (videoTracks == 0) {
-      ok(!desc.sdp.includes("m=video"), "video m-line is absent from SDP");
-    } else {
-      ok(desc.sdp.includes("m=video"), "video m-line is present in SDP");
-      if (this.h264) {
-        ok(desc.sdp.includes("a=rtpmap:126 H264/90000"), "H.264 codec is present in SDP");
-      } else {
-        ok(desc.sdp.includes("a=rtpmap:120 VP8/90000"), "VP8 codec is present in SDP");
-      }
-      ok(desc.sdp.includes("a=rtcp-mux"), "RTCP Mux is offered in SDP");
-    }
-
-  },
-
   /**
    * Check that media flow is present on the given media element by waiting for
    * it to reach ready state HAVE_ENOUGH_DATA and progress time further than
    * the start of the check.
    *
    * This ensures, that the stream being played is producing
    * data and that at least one video frame has been displayed.
    *
@@ -1686,47 +1623,55 @@ PeerConnectionWrapper.prototype = {
    * 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
    *        The stats to check for ICE candidate pairs
    * @param {object} counters
    *        The counters for media and data tracks based on constraints
-   * @param {object} answer
-   *        The SDP answer to check for SDP bundle support
+   * @param {object} testOptions
+   *        The test options object from the PeerConnectionTest
    */
   checkStatsIceConnections : function(stats,
-      offerConstraintsList, offerOptions, answer) {
+      offerConstraintsList, offerOptions, testOptions) {
     var numIceConnections = 0;
     Object.keys(stats).forEach(key => {
       if ((stats[key].type === "candidatepair") && stats[key].selected) {
         numIceConnections += 1;
       }
     });
     info("ICE connections according to stats: " + numIceConnections);
     isnot(numIceConnections, 0, "Number of ICE connections according to stats is not zero");
-    if (answer.sdp.includes('a=group:BUNDLE')) {
-      is(numIceConnections, 1, "stats reports exactly 1 ICE connection");
+    if (testOptions.bundle) {
+      if (testOptions.rtcpmux) {
+        is(numIceConnections, 1, "stats reports exactly 1 ICE connection");
+      } else {
+        is(numIceConnections, 2, "stats report exactly 2 ICE connections for media and RTCP");
+      }
     } else {
       // This code assumes that no media sections have been rejected due to
       // codec mismatch or other unrecoverable negotiation failures.
       var numAudioTracks =
-          this.countTracksInConstraint('audio', offerConstraintsList) ||
+          sdputils.countTracksInConstraint('audio', offerConstraintsList) ||
           ((offerOptions && offerOptions.offerToReceiveAudio) ? 1 : 0);
 
       var numVideoTracks =
-          this.countTracksInConstraint('video', offerConstraintsList) ||
+          sdputils.countTracksInConstraint('video', offerConstraintsList) ||
           ((offerOptions && offerOptions.offerToReceiveVideo) ? 1 : 0);
 
+      var numAvTracks = numAudioTracks + numVideoTracks;
+      if (!testOptions.rtcpmux) {
+        numAvTracks = 2 * numAvTracks;
+      }
       var numDataTracks = this.dataChannels.length;
 
-      var numAudioVideoDataTracks = numAudioTracks + numVideoTracks + numDataTracks;
+      var numAudioVideoDataTracks = numAvTracks + numDataTracks;
       info("expected audio + video + data tracks: " + numAudioVideoDataTracks);
-      is(numAudioVideoDataTracks, numIceConnections, "stats ICE connections matches expected A/V tracks");
+      is(numIceConnections, numAudioVideoDataTracks, "stats ICE connections matches expected A/V tracks");
     }
   },
 
   expectNegotiationNeeded : function() {
     if (!this.observedNegotiationNeeded) {
       this.observedNegotiationNeeded = new Promise((resolve) => {
         this.onnegotiationneeded = resolve;
       });
@@ -1784,17 +1729,18 @@ PeerConnectionWrapper.prototype = {
 function addLoadEvent() {}
 
 var scriptsReady = Promise.all([
   "/tests/SimpleTest/SimpleTest.js",
   "head.js",
   "templates.js",
   "turnConfig.js",
   "dataChannel.js",
-  "network.js"
+  "network.js",
+  "sdpUtils.js"
 ].map(script  => {
   var el = document.createElement("script");
   if (typeof scriptRelativePath === 'string' && script.charAt(0) !== '/') {
     script = scriptRelativePath + script;
   }
   el.src = script;
   document.head.appendChild(el);
   return new Promise(r => { el.onload = r; el.onerror = r; });
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/sdpUtils.js
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var sdputils = {
+
+checkSdpAfterEndOfTrickle: function(sdp, testOptions, label) {
+  if (!sdp) {
+    ok(false, label + " missing end-of-candidates SDP");
+    return;
+  }
+  info("EOC-SDP: " + JSON.stringify(sdp));
+
+  ok(sdp.sdp.includes("a=end-of-candidates"), label + ": SDP contains end-of-candidates");
+  ok(!sdp.sdp.includes("c=IN IP4 0.0.0.0"), label + ": SDP contains non-zero IP c line");
+
+  if (testOptions.rtcpmux) {
+    ok(sdp.sdp.includes("a=rtcp-mux"), label + ": SDP contains rtcp-mux");
+  } else {
+    ok(sdp.sdp.includes("a=rtcp:"), label + ": SDP contains rtcp port");
+  }
+},
+
+// Also remove mode 0 if it's offered
+// Note, we don't bother removing the fmtp lines, which makes a good test
+// for some SDP parsing issues.
+removeVP8: function(sdp) {
+  var updated_sdp = sdp.replace("a=rtpmap:120 VP8/90000\r\n","");
+  updated_sdp = updated_sdp.replace("RTP/SAVPF 120 126 97\r\n","RTP/SAVPF 126 97\r\n");
+  updated_sdp = updated_sdp.replace("RTP/SAVPF 120 126\r\n","RTP/SAVPF 126\r\n");
+  updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack\r\n","");
+  updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack pli\r\n","");
+  updated_sdp = updated_sdp.replace("a=rtcp-fb:120 ccm fir\r\n","");
+  return updated_sdp;
+},
+
+removeRtcpMux: function(sdp) {
+  var updated_sdp = sdp.replace(/a=rtcp-mux\r\n/g,"");
+  return updated_sdp;
+},
+
+removeBundle: function(sdp) {
+  var updated_sdp = sdp.replace(/a=group:BUNDLE .*\r\n/g,
+                                "");
+  return updated_sdp;
+},
+
+verifySdp: function(desc, expectedType, offerConstraintsList, offerOptions,
+                    testOptions) {
+  info("Examining this SessionDescription: " + JSON.stringify(desc));
+  info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
+  info("offerOptions: " + JSON.stringify(offerOptions));
+  ok(desc, "SessionDescription is not null");
+  is(desc.type, expectedType, "SessionDescription type is " + expectedType);
+  ok(desc.sdp.length > 10, "SessionDescription body length is plausible");
+  ok(desc.sdp.includes("a=ice-ufrag"), "ICE username is present in SDP");
+  ok(desc.sdp.includes("a=ice-pwd"), "ICE password is present in SDP");
+  ok(desc.sdp.includes("a=fingerprint"), "ICE fingerprint is present in SDP");
+  //TODO: update this for loopback support bug 1027350
+  ok(!desc.sdp.includes(LOOPBACK_ADDR), "loopback interface is absent from SDP");
+  var requiresTrickleIce = !desc.sdp.includes("a=candidate");
+  if (requiresTrickleIce) {
+    info("at least one ICE candidate is present in SDP");
+  } else {
+    info("No ICE candidate in SDP -> requiring trickle ICE");
+  }
+
+  //TODO: how can we check for absence/presence of m=application?
+
+  var audioTracks =
+      sdputils.countTracksInConstraint('audio', offerConstraintsList) ||
+      ((offerOptions && offerOptions.offerToReceiveAudio) ? 1 : 0);
+
+  info("expected audio tracks: " + audioTracks);
+  if (audioTracks == 0) {
+    ok(!desc.sdp.includes("m=audio"), "audio m-line is absent from SDP");
+  } else {
+    ok(desc.sdp.includes("m=audio"), "audio m-line is present in SDP");
+    ok(desc.sdp.includes("a=rtpmap:109 opus/48000/2"), "OPUS codec is present in SDP");
+    //TODO: ideally the rtcp-mux should be for the m=audio, and not just
+    //      anywhere in the SDP (JS SDP parser bug 1045429)
+    is(testOptions.rtcpmux, desc.sdp.includes("a=rtcp-mux"), "RTCP Mux is offered in SDP");
+  }
+
+  var videoTracks =
+      sdputils.countTracksInConstraint('video', offerConstraintsList) ||
+      ((offerOptions && offerOptions.offerToReceiveVideo) ? 1 : 0);
+
+  info("expected video tracks: " + videoTracks);
+  if (videoTracks == 0) {
+    ok(!desc.sdp.includes("m=video"), "video m-line is absent from SDP");
+  } else {
+    ok(desc.sdp.includes("m=video"), "video m-line is present in SDP");
+    if (testOptions.h264) {
+      ok(desc.sdp.includes("a=rtpmap:126 H264/90000"), "H.264 codec is present in SDP");
+    } else {
+      ok(desc.sdp.includes("a=rtpmap:120 VP8/90000"), "VP8 codec is present in SDP");
+    }
+    is(testOptions.rtcpmux, desc.sdp.includes("a=rtcp-mux"), "RTCP Mux is offered in SDP");
+  }
+
+  return requiresTrickleIce;
+},
+
+
+checkForDuplicatedPortsInSdp: function(offer, answer) {
+  //TODO this only works with SDP which includes the ICE candidates!
+  var re = /a=candidate.* (UDP|TCP) [\d]+ ([\d\.]+) ([\d]+) typ host/g;
+
+  var _sdpCandidatesIntoArray = sdp => {
+    var regexArray = [];
+    var resultArray = [];
+    while ((regexArray = re.exec(sdp)) !== null) {
+      info("regexArray: " + regexArray);
+      if ((regexArray[1] === "TCP") && (regexArray[3] === "9")) {
+        // As both sides can advertise TCP active connection on port 9 lets
+        // ignore them all together
+        info("Ignoring TCP candidate on port 9");
+        continue;
+      }
+      var triple = regexArray[1] + ":" + regexArray[2] + ":" + regexArray[3];
+      info("triple: " + triple);
+      if (resultArray.indexOf(triple) !== -1) {
+        dump("SDP: " + sdp.replace(/[\r]/g, '') + "\n");
+        ok(false, "This Transport:IP:Port " + triple + " appears twice in the SDP above!");
+      }
+      resultArray.push(triple);
+    }
+    return resultArray;
+  };
+
+  var offerTriples = _sdpCandidatesIntoArray(offer.sdp);
+  info("Offer ICE host candidates: " + JSON.stringify(offerTriples));
+
+  var answerTriples = _sdpCandidatesIntoArray(answer.sdp);
+  info("Answer ICE host candidates: " + JSON.stringify(answerTriples));
+
+  offerTriples.forEach(o => {
+    if (answerTriples.indexOf(o) !== -1) {
+      dump("SDP offer: " + offer.sdp.replace(/[\r]/g, '') + "\n");
+      dump("SDP answer: " + answer.sdp.replace(/[\r]/g, '') + "\n");
+      ok(false, "This IP:Port " + o + " appears in SDP offer and answer!");
+    }
+  });
+},
+
+/**
+ * Counts the amount of audio tracks in a given media constraint.
+ *
+ * @param constraints
+ *        The contraint to be examined.
+ */
+countTracksInConstraint: function(type, constraints) {
+  if (!Array.isArray(constraints)) {
+    return 0;
+  }
+  return constraints.reduce((sum, c) => sum + (c[type] ? 1 : 0), 0);
+},
+
+};
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -203,30 +203,20 @@ var commandsGetUserMedia = [
   function PC_REMOTE_GUM(test) {
     return test.pcRemote.getAllUserMedia(test.pcRemote.constraints);
   },
 ];
 
 var commandsPeerConnectionOfferAnswer = [
   function PC_LOCAL_SETUP_ICE_HANDLER(test) {
     test.pcLocal.setupIceCandidateHandler(test);
-    if (test.steeplechase) {
-      test.pcLocal.endOfTrickleIce.then(() => {
-        send_message({"type": "end_of_trickle_ice"});
-      });
-    }
   },
 
   function PC_REMOTE_SETUP_ICE_HANDLER(test) {
     test.pcRemote.setupIceCandidateHandler(test);
-    if (test.steeplechase) {
-      test.pcRemote.endOfTrickleIce.then(() => {
-        send_message({"type": "end_of_trickle_ice"});
-      });
-    }
   },
 
   function PC_LOCAL_STEEPLECHASE_SIGNAL_EXPECTED_LOCAL_TRACKS(test) {
     if (test.steeplechase) {
       send_message({"type": "local_expected_tracks",
                     "expected_tracks": test.pcLocal.expectedLocalTrackInfoById});
     }
   },
@@ -311,25 +301,27 @@ var commandsPeerConnectionOfferAnswer = 
     return test.setRemoteDescription(test.pcRemote, test._local_offer, HAVE_REMOTE_OFFER)
       .then(() => {
         is(test.pcRemote.signalingState, HAVE_REMOTE_OFFER,
            "signalingState after remote setRemoteDescription is 'have-remote-offer'");
       });
   },
 
   function PC_LOCAL_SANE_LOCAL_SDP(test) {
-    test.pcLocal.verifySdp(test._local_offer, "offer",
-                           test._offer_constraints, test._offer_options,
-                           true);
+    test.pcLocal.localRequiresTrickleIce =
+      sdputils.verifySdp(test._local_offer, "offer",
+                         test._offer_constraints, test._offer_options,
+                         test.testOptions);
   },
 
   function PC_REMOTE_SANE_REMOTE_SDP(test) {
-    test.pcRemote.verifySdp(test._local_offer, "offer",
-                            test._offer_constraints, test._offer_options,
-                            false);
+    test.pcRemote.remoteRequiresTrickleIce =
+      sdputils.verifySdp(test._local_offer, "offer",
+                         test._offer_constraints, test._offer_options,
+                         test.testOptions);
   },
 
   function PC_REMOTE_CREATE_ANSWER(test) {
     return test.createAnswer(test.pcRemote)
       .then(answer => {
         is(test.pcRemote.signalingState, HAVE_REMOTE_OFFER,
            "Remote createAnswer does not change signaling state");
         if (test.steeplechase) {
@@ -337,56 +329,16 @@ var commandsPeerConnectionOfferAnswer = 
                         "answer": test.originalAnswer,
                         "answer_constraints": test.pcRemote.constraints});
           test._remote_answer = test.pcRemote._last_answer;
           test._answer_constraints = test.pcRemote.constraints;
         }
       });
   },
 
-  function PC_REMOTE_CHECK_FOR_DUPLICATED_PORTS_IN_SDP(test) {
-    var re = /a=candidate.* (UDP|TCP) [\d]+ ([\d\.]+) ([\d]+) typ host/g;
-
-    var _sdpCandidatesIntoArray = sdp => {
-      var regexArray = [];
-      var resultArray = [];
-      while ((regexArray = re.exec(sdp)) !== null) {
-        info("regexArray: " + regexArray);
-        if ((regexArray[1] === "TCP") && (regexArray[3] === "9")) {
-          // As both sides can advertise TCP active connection on port 9 lets
-          // ignore them all together
-          info("Ignoring TCP candidate on port 9");
-          continue;
-        }
-        var triple = regexArray[1] + ":" + regexArray[2] + ":" + regexArray[3];
-        info("triple: " + triple);
-        if (resultArray.indexOf(triple) !== -1) {
-          dump("SDP: " + sdp.replace(/[\r]/g, '') + "\n");
-          ok(false, "This Transport:IP:Port " + triple + " appears twice in the SDP above!");
-        }
-        resultArray.push(triple);
-      }
-      return resultArray;
-    };
-
-    var offerTriples = _sdpCandidatesIntoArray(test._local_offer.sdp);
-    info("Offer ICE host candidates: " + JSON.stringify(offerTriples));
-
-    var answerTriples = _sdpCandidatesIntoArray(test.originalAnswer.sdp);
-    info("Answer ICE host candidates: " + JSON.stringify(answerTriples));
-
-    offerTriples.forEach(o => {
-      if (answerTriples.indexOf(o) !== -1) {
-        dump("SDP offer: " + test._local_offer.sdp.replace(/[\r]/g, '') + "\n");
-        dump("SDP answer: " + test.originalAnswer.sdp.replace(/[\r]/g, '') + "\n");
-        ok(false, "This IP:Port " + o + " appears in SDP offer and answer!");
-      }
-    });
-  },
-
   function PC_REMOTE_SET_LOCAL_DESCRIPTION(test) {
     return test.setLocalDescription(test.pcRemote, test.originalAnswer, STABLE)
       .then(() => {
         is(test.pcRemote.signalingState, STABLE,
            "signalingState after remote setLocalDescription is 'stable'");
       });
   },
 
@@ -407,24 +359,26 @@ var commandsPeerConnectionOfferAnswer = 
   function PC_LOCAL_SET_REMOTE_DESCRIPTION(test) {
     return test.setRemoteDescription(test.pcLocal, test._remote_answer, STABLE)
       .then(() => {
         is(test.pcLocal.signalingState, STABLE,
            "signalingState after local setRemoteDescription is 'stable'");
       });
   },
   function PC_REMOTE_SANE_LOCAL_SDP(test) {
-    test.pcRemote.verifySdp(test._remote_answer, "answer",
-                            test._offer_constraints, test._offer_options,
-                            true);
+    test.pcRemote.localRequiresTrickleIce =
+      sdputils.verifySdp(test._remote_answer, "answer",
+                         test._offer_constraints, test._offer_options,
+                         test.testOptions);
   },
   function PC_LOCAL_SANE_REMOTE_SDP(test) {
-    test.pcLocal.verifySdp(test._remote_answer, "answer",
-                           test._offer_constraints, test._offer_options,
-                           false);
+    test.pcLocal.remoteRequiresTrickleIce =
+      sdputils.verifySdp(test._remote_answer, "answer",
+                         test._offer_constraints, test._offer_options,
+                         test.testOptions);
   },
 
   function PC_LOCAL_WAIT_FOR_ICE_CONNECTED(test) {
     return waitForIceConnected(test, test.pcLocal);
   },
 
   function PC_REMOTE_WAIT_FOR_ICE_CONNECTED(test) {
     return waitForIceConnected(test, test.pcRemote);
@@ -478,58 +432,76 @@ var commandsPeerConnectionOfferAnswer = 
     });
   },
 
   function PC_LOCAL_CHECK_ICE_CONNECTIONS(test) {
     return test.pcLocal.getStats().then(stats => {
       test.pcLocal.checkStatsIceConnections(stats,
                                             test._offer_constraints,
                                             test._offer_options,
-                                            test._remote_answer);
+                                            test.testOptions);
     });
   },
 
   function PC_REMOTE_CHECK_ICE_CONNECTIONS(test) {
     return test.pcRemote.getStats().then(stats => {
       test.pcRemote.checkStatsIceConnections(stats,
                                              test._offer_constraints,
                                              test._offer_options,
-                                             test.originalAnswer);
+                                             test.testOptions);
     });
   },
 
   function PC_LOCAL_CHECK_MSID(test) {
     return test.pcLocal.checkMsids();
   },
   function PC_REMOTE_CHECK_MSID(test) {
     return test.pcRemote.checkMsids();
   },
 
   function PC_LOCAL_CHECK_STATS(test) {
     return checkAllTrackStats(test.pcLocal);
   },
   function PC_REMOTE_CHECK_STATS(test) {
     return checkAllTrackStats(test.pcRemote);
   },
-  function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
-    return test.pcLocal.endOfTrickleIce;
+  function PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) {
+    return Promise.race([
+      test.pcLocal.endOfTrickleSdp,
+      Promise.reject("No SDP")
+    ])
+    .then(sdp => sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label),
+          () => info("pcLocal: SDP after end-of-candidates missing"));
   },
-  function PC_REMOTE_WAIT_FOR_END_OF_TRICKLE(test) {
-    return test.pcRemote.endOfTrickleIce;
+  function PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) {
+    return Promise.race([
+      test.pcRemote.endOfTrickleSdp,
+      Promise.reject("No SDP")
+    ])
+    .then(sdp => sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label),
+          () => info("pcRemote: SDP after end-of-candidates missing"));
   }
 ];
 
+function PC_LOCAL_REMOVE_VP8_FROM_OFFER(test) {
+  isnot(test.originalOffer.sdp.search("H264/90000"), -1, "H.264 should be present in the SDP offer");
+  test.originalOffer.sdp = sdputils.removeVP8(test.originalOffer.sdp);
+  info("Updated H264 only offer: " + JSON.stringify(test.originalOffer));
+};
+
 function PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER(test) {
-  test.originalOffer.sdp = test.originalOffer.sdp.replace(
-      /a=group:BUNDLE .*\r\n/g,
-      ""
-      );
+  test.originalOffer.sdp = sdputils.removeBundle(test.originalOffer.sdp);
   info("Updated no bundle offer: " + JSON.stringify(test.originalOffer));
 };
 
+function PC_LOCAL_REMOVE_RTCPMUX_FROM_OFFER(test) {
+  test.originalOffer.sdp = sdputils.removeRtcpMux(test.originalOffer.sdp);
+  info("Updated no RTCP-Mux offer: " + JSON.stringify(test.originalOffer));
+};
+
 var addRenegotiation = (chain, commands, checks) => {
   chain.append(commands);
   chain.append(commandsPeerConnectionOfferAnswer);
   if (checks) {
     chain.append(checks);
   }
 };
 
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
@@ -7,27 +7,21 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1016476",
     title: "Basic data channel audio/video connection without bundle"
   });
 
 var test;
-runNetworkTest(function () {
-  test = new PeerConnectionTest();
+runNetworkTest(function (options) {
+  options = options || { };
+  options.bundle = false;
+  test = new PeerConnectionTest(options);
   addInitialDataChannel(test.chain);
-  test.chain.insertAfter("PC_LOCAL_CREATE_OFFER", [
-    function PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER(test) {
-      // Just replace a=group:BUNDLE with something that will be ignored.
-      test.originalOffer.sdp = test.originalOffer.sdp.replace(
-        "a=group:BUNDLE",
-        "a=foo:");
-    }
-  ]);
   test.setMediaConstraints([{audio: true}, {video: true}],
                            [{audio: true}, {video: true}]);
   test.run();
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_addDataChannelNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addDataChannelNoBundle.html
@@ -8,33 +8,32 @@
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add DataChannel"
   });
 
   var test;
   runNetworkTest(function (options) {
+    options = options || { };
+    options.bundle = false;
     test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
                      commandsCreateDataChannel.concat(
                        [
                          function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
                            test.pcLocal.iceCheckingRestartExpected = true;
                          },
                          function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
                            test.pcRemote.iceCheckingRestartExpected = true;
                          },
                        ]
                       ),
                      commandsCheckDataChannel);
 
-    test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
-                              PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
-
     // Insert before the second PC_LOCAL_CHECK_MEDIA_TRACKS
     test.chain.insertBefore('PC_LOCAL_CHECK_MEDIA_TRACKS',
                             commandsWaitForDataChannel,
                             false,
                             1);
 
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
@@ -8,16 +8,18 @@
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add second audio stream, no bundle"
   });
 
   var test;
   runNetworkTest(function (options) {
+    options = options || { };
+    options.bundle = false;
     test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_ADD_SECOND_STREAM(test) {
           test.setMediaConstraints([{audio: true}, {audio: true}],
                                    [{audio: true}]);
           // Since this is a NoBundle variant, adding a track will cause us to
           // go back to checking.
@@ -25,19 +27,16 @@
           return test.pcLocal.getAllUserMedia([{audio: true}]);
         },
         function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
           test.pcRemote.iceCheckingRestartExpected = true;
         },
       ]
     );
 
-    test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
-                              PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
-
     // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
@@ -8,16 +8,18 @@
 <script type="application/javascript">
   createHTML({
     bug: "1017888",
     title: "Renegotiation: add second video stream, no bundle"
   });
 
   var test;
   runNetworkTest(function (options) {
+    options = options || { };
+    options.bundle = false;
     test = new PeerConnectionTest(options);
     addRenegotiation(test.chain,
       [
         function PC_LOCAL_ADD_SECOND_STREAM(test) {
           test.setMediaConstraints([{video: true}, {video: true}],
                                    [{video: true}]);
           // Since this is a NoBundle variant, adding a track will cause us to
           // go back to checking.
@@ -25,19 +27,16 @@
           return test.pcLocal.getAllUserMedia([{video: true}]);
         },
         function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
           test.pcRemote.iceCheckingRestartExpected = true;
         },
       ]
     );
 
-    test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
-                              PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
-
     // TODO(bug 1093835): figure out how to verify if media flows through the new stream
     test.setMediaConstraints([{video: true}], [{video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
copy from dom/media/tests/mochitest/test_peerConnection_basicAudio.html
copy to dom/media/tests/mochitest/test_peerConnection_basicAudioRequireEOC.html
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioRequireEOC.html
@@ -2,22 +2,34 @@
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
-    bug: "796892",
-    title: "Basic audio-only peer connection"
+    bug: "1167443",
+    title: "Basic audio-only peer connection which waits for end-of-candidates"
   });
 
   var test;
   runNetworkTest(function (options) {
     test = new PeerConnectionTest(options);
+    test.chain.replace("PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+      function PC_LOCAL_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+        return test.pcLocal.endOfTrickleSdp .then(sdp =>
+          sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label));
+      }
+    ]);
+    test.chain.replace("PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+      function PC_REMOTE_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+        return test.pcRemote.endOfTrickleSdp .then(sdp =>
+          sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label));
+      }
+    ]);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundle.html
@@ -7,20 +7,19 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1016476",
     title: "Basic audio/video peer connection with no Bundle"
   });
 
   runNetworkTest(options => {
+    options = options || { };
+    options.bundle = false;
     var test = new PeerConnectionTest(options);
-    test.chain.insertAfter(
-      'PC_LOCAL_CREATE_OFFER',
-      [PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER]);
     test.setMediaConstraints([{audio: true}, {video: true}],
                              [{audio: true}, {video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
copy from dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html
copy to dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html
@@ -2,23 +2,38 @@
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
-    bug: "796890",
-    title: "Basic audio/video (separate) peer connection"
+    bug: "1167443",
+    title: "Basic audio & video call with disabled bundle and disbaled RTCP-Mux"
   });
 
   var test;
   runNetworkTest(function (options) {
+    options = options || { };
+    options.bundle = false;
+    options.rtcpmux = false;
     test = new PeerConnectionTest(options);
+    test.chain.replace("PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+      function PC_LOCAL_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+        return test.pcLocal.endOfTrickleSdp .then(sdp =>
+          sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label));
+      }
+    ]);
+    test.chain.replace("PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+      function PC_REMOTE_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+        return test.pcRemote.endOfTrickleSdp .then(sdp =>
+          sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label));
+      }
+    ]);
     test.setMediaConstraints([{audio: true}, {video: true}],
                              [{audio: true}, {video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
copy from dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html
copy to dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoRtcpMux.html
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoRtcpMux.html
@@ -2,23 +2,37 @@
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
-    bug: "796890",
-    title: "Basic audio/video (separate) peer connection"
+    bug: "1167443",
+    title: "Basic audio & video call with disabled RTCP-Mux"
   });
 
   var test;
   runNetworkTest(function (options) {
+    options = options || { };
+    options.rtcpmux = false;
     test = new PeerConnectionTest(options);
+    test.chain.replace("PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+      function PC_LOCAL_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+        return test.pcLocal.endOfTrickleSdp .then(sdp =>
+          sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label));
+      }
+    ]);
+    test.chain.replace("PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+      function PC_REMOTE_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+        return test.pcRemote.endOfTrickleSdp .then(sdp =>
+          sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label));
+      }
+    ]);
     test.setMediaConstraints([{audio: true}, {video: true}],
                              [{audio: true}, {video: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>