Bug 1393306 - Test deprecation warning in 57 for removal of stat.isRemote in 58. draft
authorJan-Ivar Bruaroey <jib@mozilla.com>
Mon, 28 Aug 2017 12:54:51 -0400
changeset 826548 c34b4ae5e569fabfc0ebba9bd3e923fda58612df
parent 826547 6ac36e71beb5cf62ca1daca14bd99ef1309ce901
push id118358
push userjbruaroey@mozilla.com
push dateSat, 04 Aug 2018 02:35:03 +0000
bugs1393306
milestone63.0a1
Bug 1393306 - Test deprecation warning in 57 for removal of stat.isRemote in 58. MozReview-Commit-ID: IL8WsU22QCi
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_peerConnection_stats_isRemoteWarning57.html
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -330,16 +330,18 @@ skip-if = (android_version == '18') # an
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_threeUnbundledConnections.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_selftest.html]
 # Bug 1227781: Crash with bogus TURN server.
 [test_peerConnection_bug1227781.html]
 [test_peerConnection_stats.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858)
+[test_peerConnection_stats_isRemoteWarning57.html]
+skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_sender_and_receiver_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_trackless_sender_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_verifyDescriptions.html]
 skip-if = (android_version == '18')
 [test_fingerprinting_resistance.html]
 [test_getUserMedia_nonDefaultRate.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_stats_isRemoteWarning57.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+createHTML({title: "Test 57 stat.isRemote deprecate warning", bug: "1393306"});
+
+// In 58, remote stats will move from "outbound-rtp"/"inbound-rtp" to
+// "remote-outbound-rtp"/"remote-inbound-rtp" and isRemote is gone (Bug 1380555).
+//
+// See https://blog.mozilla.org/webrtc/getstats-firefox-58/ for more.
+//
+// Test the deprecation warnings added to 57 to detect safe vs breaking access
+// of remote stats. In short, sites that rely on remoteId will be fine:
+//
+// stats.get([...stats.values()].find(s => s.type == "outbound-rtp" &&
+//                                    !s.isRemote).remoteId).packetsReceived;
+//
+// but sites that check isRemote for true, will break in 58:
+//
+// [...stats.values()].find(s => s.type == "inbound-rtp" &&
+//                          s.isRemote).packetsReceived; // will break in 58
+//
+// Test exercises carefully placed mouse-traps to warn on the latter case only.
+
+function hasRtcps(stats) {
+  return [...stats.values()].find(s => s.type == "inbound-rtp" && s.isRemote) &&
+         [...stats.values()].find(s => s.type == "outbound-rtp" && s.isRemote);
+}
+
+async function testVideoCall(testCallback) {
+  let pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
+
+  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
+  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
+
+  pc1.onnegotiationneeded = async e => {
+    await pc1.setLocalDescription(await pc1.createOffer());
+    await pc2.setRemoteDescription(pc1.localDescription);
+    await pc2.setLocalDescription(await pc2.createAnswer());
+    await pc1.setRemoteDescription(pc2.localDescription);
+  };
+
+  let stream = await navigator.mediaDevices.getUserMedia({video: true});
+  pc1.addTrack(stream.getTracks()[0], stream);
+  pc2.addTrack(stream.getTracks()[0], stream);
+
+  while (!hasRtcps(await pc1.getStats())) {
+    await wait(100);
+  }
+
+  await testCallback(pc1, pc2);
+  pc1.close();
+  pc2.close();
+  stream.getTracks().forEach(track => track.stop());
+}
+
+function warningIsTurnedOn(stats) {
+  switch([...stats.values()][0].toString()) {
+    case "[object RTCStatsDeprecationWarningEntry]": {
+      return true;
+    }
+    case "[object Object]": {
+      return false;
+    }
+    default: {
+      ok(false, "Something went wrong!");
+    }
+  }
+}
+
+// Example reading of stats, not meant to be exhaustive.
+let mb = bytes => (bytes/1024000).toFixed(2);
+let dumpHeader = o => `${o.type} ${o.mediaType}\
+ ${new Date(o.timestamp).toTimeString()}<br>SSRC: ${o.ssrc} `;
+let dumpOutbound = o => dumpHeader(o) + `Sent: ${o.packetsSent} \
+packets (${mb(o.bytesSent)} MB)<br>Dropped frames: ${o.droppedFrames}<br>`;
+let dumpInbound = o => dumpHeader(o) + `Received: ${o.packetsReceived} \
+packets (${mb(o.bytesReceived)} MB) Lost: ${o.packetsLost}
+Discarded packets: ${o.discardedPackets} Jitter: ${o.jitter}<br>`;
+
+runNetworkTest(async options => {
+  await testVideoCall(async (pc1, pc2) => {
+    // Test typical read pattern avoiding remote stats. Must not warn.
+    {
+      let stats = await pc1.getStats();
+      for (let stat of stats.values()) {
+        switch (stat.type) {
+          case "outbound-rtp": {
+            if (stat.isRemote) continue;
+            info("Local: " + dumpOutbound(stat));
+            break;
+          }
+          case "inbound-rtp": {
+            if (stat.isRemote) continue;
+            info("Local: " + dumpInbound(stat));
+            break;
+          }
+        }
+      }
+
+      ok(warningIsTurnedOn(await pc1.getStats()),
+         "Accessing local stats must not trigger or shut down warning.");
+    }
+
+    // Reading remote stats directly must warn.
+    //
+    // NOTE: Tried and failed to detect this warning. It doesn't show up in
+    // "console-api-log-event" for some reason, so we're merely testing for
+    // warning OR warning being turned off, to exercise code. Actual warning
+    // must be observed with manual testing).
+
+    hasRtcps(await pc1.getStats()).ssrc;
+    ok(!warningIsTurnedOn(await pc1.getStats()),
+       "Access of remote stats trips warning (or turns it off, can't tell).");
+
+    // Use pc2 for remaining tests.
+    pc1 = pc2;
+
+    // Test getting remote stats through remoteId. Must turn off warning.
+    {
+      let stats = await pc1.getStats();
+      let local = [...stats.values()].find(s => s.type == "outbound-rtp" &&
+                                           !s.isRemote);
+
+      ok(warningIsTurnedOn(await pc1.getStats()), "Warning on still.");
+
+      let remote = stats.get(local.remoteId);
+
+      ok(!warningIsTurnedOn(await pc1.getStats()),
+         "Warning must be off (shouldn't trigger, but can't tell).");
+    }
+  });
+
+  await testVideoCall(async (pc1, pc2) => {
+    ok(warningIsTurnedOn(await pc1.getStats()), "Warning is on initially.");
+    // Check that we stop warning if we see JSON.stringify().
+    JSON.stringify([...(await pc1.getStats()).values()]);
+    ok(!warningIsTurnedOn(await pc1.getStats()),
+       "Must give up warning in the face of JSON.stringify.");
+  });
+  finish();
+});
+
+</script>
+</pre>
+</body>
+</html>