summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-mandatory-getStats.https.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-mandatory-getStats.https.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCPeerConnection-mandatory-getStats.https.html277
1 files changed, 277 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-mandatory-getStats.https.html b/testing/web-platform/tests/webrtc/RTCPeerConnection-mandatory-getStats.https.html
new file mode 100644
index 0000000000..099fba8eaf
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-mandatory-getStats.https.html
@@ -0,0 +1,277 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>Mandatory-to-implement stats compliance (a subset of webrtc-stats)</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="dictionary-helper.js"></script>
+<script src="RTCStats-helper.js"></script>
+<script>
+'use strict';
+
+// From https://w3c.github.io/webrtc-pc/#mandatory-to-implement-stats
+
+const mandatory = {
+ RTCRtpStreamStats: [
+ "ssrc",
+ "kind",
+ "transportId",
+ "codecId",
+ ],
+ RTCReceivedRtpStreamStats: [
+ "packetsReceived",
+ "packetsLost",
+ "jitter",
+ ],
+ RTCInboundRtpStreamStats: [
+ "trackIdentifier",
+ "remoteId",
+ "framesDecoded",
+ "framesDropped",
+ "nackCount",
+ "framesReceived",
+ "bytesReceived",
+ "totalAudioEnergy",
+ "totalSamplesDuration",
+ "packetsDiscarded",
+ ],
+ RTCRemoteInboundRtpStreamStats: [
+ "localId",
+ "roundTripTime",
+ ],
+ RTCSentRtpStreamStats: [
+ "packetsSent",
+ "bytesSent"
+ ],
+ RTCOutboundRtpStreamStats: [
+ "remoteId",
+ "framesEncoded",
+ "nackCount",
+ "framesSent"
+ ],
+ RTCRemoteOutboundRtpStreamStats: [
+ "localId",
+ "remoteTimestamp",
+ ],
+ RTCPeerConnectionStats: [
+ "dataChannelsOpened",
+ "dataChannelsClosed",
+ ],
+ RTCDataChannelStats: [
+ "label",
+ "protocol",
+ "dataChannelIdentifier",
+ "state",
+ "messagesSent",
+ "bytesSent",
+ "messagesReceived",
+ "bytesReceived",
+ ],
+ RTCMediaSourceStats: [
+ "trackIdentifier",
+ "kind"
+ ],
+ RTCAudioSourceStats: [
+ "totalAudioEnergy",
+ "totalSamplesDuration"
+ ],
+ RTCVideoSourceStats: [
+ "width",
+ "height",
+ "framesPerSecond"
+ ],
+ RTCCodecStats: [
+ "payloadType",
+ /* codecType is part of MTI but is not systematically set
+ per https://www.w3.org/TR/webrtc-stats/#dom-rtccodecstats-codectype
+ If the dictionary member is not present, it means that
+ this media format can be both encoded and decoded. */
+ // "codecType",
+ "mimeType",
+ "clockRate",
+ "channels",
+ "sdpFmtpLine",
+ ],
+ RTCTransportStats: [
+ "bytesSent",
+ "bytesReceived",
+ "selectedCandidatePairId",
+ "localCertificateId",
+ "remoteCertificateId",
+ ],
+ RTCIceCandidatePairStats: [
+ "transportId",
+ "localCandidateId",
+ "remoteCandidateId",
+ "state",
+ "nominated",
+ "bytesSent",
+ "bytesReceived",
+ "totalRoundTripTime",
+ "currentRoundTripTime"
+ ],
+ RTCIceCandidateStats: [
+ "address",
+ "port",
+ "protocol",
+ "candidateType",
+ "url",
+ ],
+ RTCCertificateStats: [
+ "fingerprint",
+ "fingerprintAlgorithm",
+ "base64Certificate",
+ /* issuerCertificateId is part of MTI but is not systematically set
+ per https://www.w3.org/TR/webrtc-stats/#dom-rtccertificatestats-issuercertificateid
+ If the current certificate is at the end of the chain
+ (i.e. a self-signed certificate), this will not be set. */
+ // "issuerCertificateId",
+ ],
+};
+
+// From https://w3c.github.io/webrtc-stats/webrtc-stats.html#rtcstatstype-str*
+
+const dictionaryNames = {
+ "codec": "RTCCodecStats",
+ "inbound-rtp": "RTCInboundRtpStreamStats",
+ "outbound-rtp": "RTCOutboundRtpStreamStats",
+ "remote-inbound-rtp": "RTCRemoteInboundRtpStreamStats",
+ "remote-outbound-rtp": "RTCRemoteOutboundRtpStreamStats",
+ "csrc": "RTCRtpContributingSourceStats",
+ "peer-connection": "RTCPeerConnectionStats",
+ "data-channel": "RTCDataChannelStats",
+ "media-source": {
+ audio: "RTCAudioSourceStats",
+ video: "RTCVideoSourceStats"
+ },
+ "track": {
+ video: "RTCSenderVideoTrackAttachmentStats",
+ audio: "RTCSenderAudioTrackAttachmentStats"
+ },
+ "sender": {
+ audio: "RTCAudioSenderStats",
+ video: "RTCVideoSenderStats"
+ },
+ "receiver": {
+ audio: "RTCAudioReceiverStats",
+ video: "RTCVideoReceiverStats",
+ },
+ "transport": "RTCTransportStats",
+ "candidate-pair": "RTCIceCandidatePairStats",
+ "local-candidate": "RTCIceCandidateStats",
+ "remote-candidate": "RTCIceCandidateStats",
+ "certificate": "RTCCertificateStats",
+};
+
+// From https://w3c.github.io/webrtc-stats/webrtc-stats.html (webidl)
+
+const parents = {
+ RTCVideoSourceStats: "RTCMediaSourceStats",
+ RTCAudioSourceStats: "RTCMediaSourceStats",
+ RTCReceivedRtpStreamStats: "RTCRtpStreamStats",
+ RTCInboundRtpStreamStats: "RTCReceivedRtpStreamStats",
+ RTCRemoteInboundRtpStreamStats: "RTCReceivedRtpStreamStats",
+ RTCSentRtpStreamStats: "RTCRtpStreamStats",
+ RTCOutboundRtpStreamStats: "RTCSentRtpStreamStats",
+ RTCRemoteOutboundRtpStreamStats : "RTCSentRtpStreamStats",
+};
+
+const remaining = JSON.parse(JSON.stringify(mandatory));
+for (const dictName in remaining) {
+ remaining[dictName] = new Set(remaining[dictName]);
+}
+
+async function getAllStats(t, pc) {
+ // Try to obtain as many stats as possible, waiting up to 20 seconds for
+ // roundTripTime of RTCRemoteInboundRtpStreamStats and
+ // remoteTimestamp of RTCRemoteOutboundRtpStreamStats which can take
+ // several RTCP messages to calculate.
+ let stats;
+ let remoteInboundFound = false;
+ let remoteOutboundFound = false;
+ for (let i = 0; i < 20; i++) {
+ stats = await pc.getStats();
+ const values = [...stats.values()];
+ const [remoteInboundAudio, remoteInboundVideo] = ["audio", "video"].map(
+ kind => values.find(s =>
+ s.type == "remote-inbound-rtp" && s.kind == kind));
+ if (remoteInboundAudio && "roundTripTime" in remoteInboundAudio &&
+ remoteInboundVideo && "roundTripTime" in remoteInboundVideo) {
+ remoteInboundFound = true;
+ }
+ const [remoteOutboundAudio, remoteOutboundVideo] = ["audio", "video"].map(
+ kind => values.find(s =>
+ s.type == "remote-outbound-rtp" && s.kind == kind));
+ if (remoteOutboundAudio && "remoteTimestamp" in remoteOutboundAudio &&
+ remoteOutboundVideo && "remoteTimestamp" in remoteOutboundVideo) {
+ remoteOutboundFound = true;
+ }
+ if (remoteInboundFound && remoteOutboundFound) {
+ return stats;
+ }
+ await new Promise(r => t.step_timeout(r, 1000));
+ }
+ return stats;
+}
+
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const dc1 = pc1.createDataChannel("dummy", {negotiated: true, id: 0});
+ const dc2 = pc2.createDataChannel("dummy", {negotiated: true, id: 0});
+
+ const stream = await getNoiseStream({video: true, audio:true});
+ for (const track of stream.getTracks()) {
+ pc1.addTrack(track, stream);
+ pc2.addTrack(track, stream);
+ t.add_cleanup(() => track.stop());
+ }
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+ const stats = await getAllStats(t, pc1);
+
+ // The focus of this test is not API correctness, but rather to provide an
+ // accessible metric of implementation progress by dictionary member. We count
+ // whether we've seen each dictionary's mandatory members in getStats().
+
+ test(t => {
+ for (const stat of stats.values()) {
+ let dictName = dictionaryNames[stat.type];
+ if (!dictName) continue;
+ if (typeof dictName == "object") {
+ dictName = dictName[stat.kind];
+ }
+
+ assert_equals(typeof dictName, "string", "Test error. String.");
+ if (dictName && mandatory[dictName]) {
+ do {
+ const memberNames = mandatory[dictName];
+ const remainingNames = remaining[dictName];
+ assert_true(memberNames.length > 0, "Test error. Parent not found.");
+ for (const memberName of memberNames) {
+ if (memberName in stat) {
+ assert_not_equals(stat[memberName], undefined, "Not undefined");
+ remainingNames.delete(memberName);
+ }
+ }
+ dictName = parents[dictName];
+ } while (dictName);
+ }
+ }
+ }, "Validating stats");
+
+ for (const dictName in mandatory) {
+ for (const memberName of mandatory[dictName]) {
+ test(t => {
+ assert_true(!remaining[dictName].has(memberName),
+ `Is ${memberName} present`);
+ }, `${dictName}'s ${memberName}`);
+ }
+ }
+}, 'getStats succeeds');
+
+</script>