diff options
Diffstat (limited to 'tests/wpt/web-platform-tests/webrtc')
23 files changed, 2442 insertions, 590 deletions
diff --git a/tests/wpt/web-platform-tests/webrtc/OWNERS b/tests/wpt/web-platform-tests/webrtc/OWNERS index f2d66bf5c08..378bf87a106 100644 --- a/tests/wpt/web-platform-tests/webrtc/OWNERS +++ b/tests/wpt/web-platform-tests/webrtc/OWNERS @@ -1,3 +1,4 @@ +@snuggs @agouaillard @alvestrand @dontcallmedom diff --git a/tests/wpt/web-platform-tests/webrtc/RTCCertificate.html b/tests/wpt/web-platform-tests/webrtc/RTCCertificate.html index f5eccabf91b..e5f1749eb5b 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCCertificate.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCCertificate.html @@ -172,8 +172,9 @@ assert_equals(typeof fingerprint, 'object', 'Expect fingerprint to be an object (dictionary)'); - // Can only do simple test as the allowed values may be extended - assert_true(/^[a-zA-Z\-]+$/.test(fingerprint.algorithm), + // https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml + const algorithms = ['md2', 'md5', 'sha-1', 'sha-224', 'sha-256', 'sha-384', 'sha-512']; + assert_in_array(fingerprint.algorithm, algorithms, 'Expect fingerprint.algorithm to be string of algorithm identifier'); assert_true(/^([0-9a-f]{2}\:)+[0-9a-f]{2}$/.test(fingerprint.value), diff --git a/tests/wpt/web-platform-tests/webrtc/RTCConfiguration-iceTransportPolicy.html b/tests/wpt/web-platform-tests/webrtc/RTCConfiguration-iceTransportPolicy.html index 7387c5424cd..74e8b4b2ae7 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCConfiguration-iceTransportPolicy.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCConfiguration-iceTransportPolicy.html @@ -64,7 +64,7 @@ assert_equals(pc.getConfiguration().iceTransportPolicy, 'all'); pc.setConfiguration({ iceTransportPolicy: 'relay' }); - assert_equals(pc.getConfiguration(), iceTransportPolicy, 'relay'); + assert_equals(pc.getConfiguration().iceTransportPolicy, 'relay'); }, `setConfiguration({ iceTransportPolicy: 'relay' }) with initial iceTransportPolicy all should succeed`); test(() => { @@ -72,7 +72,7 @@ assert_equals(pc.getConfiguration().iceTransportPolicy, 'relay'); pc.setConfiguration({ iceTransportPolicy: 'all' }); - assert_equals(pc.getConfiguration(), iceTransportPolicy, 'all'); + assert_equals(pc.getConfiguration().iceTransportPolicy, 'all'); }, `setConfiguration({ iceTransportPolicy: 'all' }) with initial iceTransportPolicy relay should succeed`); test(() => { @@ -81,7 +81,7 @@ // default value for iceTransportPolicy is all pc.setConfiguration({}); - assert_equals(pc.getConfiguration(), iceTransportPolicy, 'all'); + assert_equals(pc.getConfiguration().iceTransportPolicy, 'all'); }, `setConfiguration({}) with initial iceTransportPolicy relay should set new value to all`); config_test(makePc => { diff --git a/tests/wpt/web-platform-tests/webrtc/RTCDTMFSender-helper.js b/tests/wpt/web-platform-tests/webrtc/RTCDTMFSender-helper.js index b66c4aedbda..97bb6f93479 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCDTMFSender-helper.js +++ b/tests/wpt/web-platform-tests/webrtc/RTCDTMFSender-helper.js @@ -76,8 +76,8 @@ function test_tone_change_events(testFunc, toneChanges, desc) { const now = Date.now(); const duration = now - lastEventTime; - assert_approx_equals(duration, expectedDuration, 150, - `Expect tonechange event for "${tone}" to be fired approximately after ${expectedDuration} seconds`); + assert_approx_equals(duration, expectedDuration, 250, + `Expect tonechange event for "${tone}" to be fired approximately after ${expectedDuration} milliseconds`); lastEventTime = now; diff --git a/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-getRemoteCertificates.html b/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-getRemoteCertificates.html index 0fd5340006b..80d1bfae46d 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-getRemoteCertificates.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-getRemoteCertificates.html @@ -76,7 +76,7 @@ testedTransports.add(dtlsTransport); // End the test if both dtlsTransports are tested. - if(testedTransports.has(dtlsTransport1) && testedTransports.has(dtslTransport2)) { + if(testedTransports.has(dtlsTransport1) && testedTransports.has(dtlsTransport2)) { t.done(); } }) diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.html index ce1c2827aac..97205714f07 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.html @@ -80,6 +80,8 @@ */ test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + assert_idl_attribute(pc, 'addTransceiver'); assert_throws(new TypeError(), () => pc.addTransceiver('invalid')); }, 'addTransceiver() with string argument as invalid kind should throw TypeError'); @@ -125,6 +127,7 @@ */ test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_idl_attribute(pc, 'addTransceiver'); @@ -170,6 +173,7 @@ test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_idl_attribute(pc, 'addTransceiver'); @@ -214,18 +218,24 @@ test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const transceiver = pc.addTransceiver('audio', { direction: 'sendonly' }); assert_equals(transceiver.direction, 'sendonly'); }, `addTransceiver() with direction sendonly should have result transceiver.direction be the same`); test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const transceiver = pc.addTransceiver('audio', { direction: 'inactive' }); assert_equals(transceiver.direction, 'inactive'); }, `addTransceiver() with direction inactive should have result transceiver.direction be the same`); test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + assert_idl_attribute(pc, 'addTransceiver'); assert_throws(new TypeError(), () => pc.addTransceiver('audio', { direction: 'invalid' })); @@ -238,8 +248,9 @@ */ test(t => { const pc = new RTCPeerConnection(); - const track = generateMediaStreamTrack('audio'); + t.add_cleanup(() => pc.close()); + const track = generateMediaStreamTrack('audio'); const transceiver = pc.addTransceiver(track); const { sender, receiver } = transceiver; @@ -276,8 +287,9 @@ test(t => { const pc = new RTCPeerConnection(); - const track = generateMediaStreamTrack('audio'); + t.add_cleanup(() => pc.close()); + const track = generateMediaStreamTrack('audio'); const transceiver1 = pc.addTransceiver(track); const transceiver2 = pc.addTransceiver(track); @@ -302,7 +314,6 @@ }, 'addTransceiver(track) multiple times should create multiple transceivers'); - /* 5.1. addTransceiver 6. Verify that each rid value in sendEncodings is composed only of @@ -310,8 +321,9 @@ of 16 characters. If one of the RIDs does not meet these requirements, throw a TypeError. */ - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_idl_attribute(pc, 'addTransceiver'); assert_throws(new TypeError(), () => @@ -322,8 +334,9 @@ })); }, 'addTransceiver() with rid containing invalid non-alphanumeric characters should throw TypeError'); - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_idl_attribute(pc, 'addTransceiver'); assert_throws(new TypeError(), () => @@ -334,8 +347,9 @@ })); }, 'addTransceiver() with rid longer than 16 characters should throw TypeError'); - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); pc.addTransceiver('audio', { sendEncodings: [{ rid: 'foo' @@ -353,8 +367,9 @@ Aside from rid , all read-only parameters in the RTCRtpEncodingParameters dictionaries, such as ssrc, must be left unset, or an error will be thrown. */ - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_throws('InvalidAccessError', () => pc.addTransceiver('audio', { @@ -364,8 +379,9 @@ })); }, `addTransceiver() with readonly ssrc set should throw InvalidAccessError`); - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_throws('InvalidAccessError', () => pc.addTransceiver('audio', { @@ -377,8 +393,9 @@ })); }, `addTransceiver() with readonly rtx set should throw InvalidAccessError`); - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); assert_throws('InvalidAccessError', () => pc.addTransceiver('audio', { @@ -390,8 +407,10 @@ })); }, `addTransceiver() with readonly fec set should throw InvalidAccessError`); - test(() => { + test(t => { const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + pc.addTransceiver('audio', { sendEncodings: [{ dtx: 'enabled', diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-constructor.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-constructor.html index 331eefe458d..e00d9870908 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-constructor.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-constructor.html @@ -73,7 +73,7 @@ for (const attr in initialState) { if (!window.pc) { window.pc = new RTCPeerConnection; } - assert_equals(pc[attr], initialState[attr]); + assert_equals(window.pc[attr], initialState[attr]); }, attr + ' initial value'); } </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createDataChannel.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createDataChannel.html index a446280eaaa..35e22f934f0 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createDataChannel.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createDataChannel.html @@ -375,7 +375,7 @@ promise_test(t => { assert_not_equals(channel1.id, null, 'Expect channel1.id to be assigned'); - assert_greater_than_equals(channel1.id, 0, + assert_greater_than_equal(channel1.id, 0, 'Expect channel1.id to be set to valid unsigned short'); assert_less_than(channel1.id, 65535, @@ -386,7 +386,7 @@ promise_test(t => { assert_not_equals(channel2.id, null, 'Expect channel2.id to be assigned'); - assert_greater_than_equals(channel2.id, 0, + assert_greater_than_equal(channel2.id, 0, 'Expect channel2.id to be set to valid unsigned short'); assert_less_than(channel2.id, 65535, diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive.html new file mode 100644 index 00000000000..c599b1c0d07 --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive.html @@ -0,0 +1,169 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test legacy offerToReceiveAudio/Video options</title> +<link rel="help" href="https://w3c.github.io/webrtc-pc/#legacy-configuration-extensions"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="RTCPeerConnection-helper.js"></script> +<script> + 'use strict'; + + // Run some tests for both audio and video kinds + ['audio', 'video'].forEach((kind) => { + const capsKind = kind[0].toUpperCase() + kind.slice(1); + + const offerToReceiveTrue = {}; + offerToReceiveTrue[`offerToReceive${capsKind}`] = true; + + const offerToReceiveFalse = {}; + offerToReceiveFalse[`offerToReceive${capsKind}`] = false; + + // Start testing + promise_test(t => { + const pc = new RTCPeerConnection(); + const dummy = pc.createDataChannel('foo'); // Just to have something to offer + + return pc.createOffer(offerToReceiveFalse) + .then(() => { + assert_equals(pc.getTransceivers().length, 0, + 'Expect pc to have no transceivers'); + }); + }, `createOffer() with offerToReceive${capsKind} set to false should not create a transceiver`); + + promise_test(t => { + const pc = new RTCPeerConnection(); + + return pc.createOffer(offerToReceiveTrue) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to have one transceiver'); + + const transceiver = pc.getTransceivers()[0]; + assert_equals(transceiver.direction, 'recvonly', + 'Expect transceiver to have "recvonly" direction'); + }); + }, `createOffer() with offerToReceive${capsKind} should create a "recvonly" transceiver`); + + promise_test(t => { + const pc = new RTCPeerConnection(); + + return pc.createOffer(offerToReceiveTrue) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to have one transceiver'); + + const transceiver = pc.getTransceivers()[0]; + assert_equals(transceiver.direction, 'recvonly', + 'Expect transceiver to have "recvonly" direction'); + }) + .then(() => pc.createOffer(offerToReceiveTrue)) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to still have only one transceiver'); + }) + ; + }, `offerToReceive${capsKind} option should be ignored if a non-stopped "recvonly" transceiver exists`); + + promise_test(t => { + const pc = new RTCPeerConnection(); + + return getTrackFromUserMedia(kind) + .then(([track, stream]) => { + pc.addTrack(track, stream); + return pc.createOffer(); + }) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to have one transceiver'); + + const transceiver = pc.getTransceivers()[0]; + assert_equals(transceiver.direction, 'sendrecv', + 'Expect transceiver to have "sendrecv" direction'); + }) + .then(() => pc.createOffer(offerToReceiveTrue)) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to still have only one transceiver'); + }) + ; + }, `offerToReceive${capsKind} option should be ignored if a non-stopped "sendrecv" transceiver exists`); + + promise_test(t => { + const pc = new RTCPeerConnection(); + + return getTrackFromUserMedia(kind) + .then(([track, stream]) => { + pc.addTrack(track, stream); + return pc.createOffer(offerToReceiveFalse); + }) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to have one transceiver'); + + const transceiver = pc.getTransceivers()[0]; + assert_equals(transceiver.direction, 'sendonly', + 'Expect transceiver to have "sendonly" direction'); + }) + ; + }, `offerToReceive${capsKind} set to false with a track should create a "sendonly" transceiver`); + + promise_test(t => { + const pc = new RTCPeerConnection(); + + pc.addTransceiver(kind, {direction: 'recvonly'}); + + return pc.createOffer(offerToReceiveFalse) + .then(() => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to have one transceiver'); + + const transceiver = pc.getTransceivers()[0]; + assert_equals(transceiver.direction, 'inactive', + 'Expect transceiver to have "inactive" direction'); + }) + ; + }, `offerToReceive${capsKind} set to false with a "recvonly" transceiver should change the direction to "inactive"`); + + promise_test(t => { + const pc = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + + return getTrackFromUserMedia(kind) + .then(([track, stream]) => { + pc.addTrack(track, stream); + return pc.createOffer(); + }) + .then((offer) => pc.setLocalDescription(offer)) + .then(() => pc2.setRemoteDescription(pc.localDescription)) + .then(() => pc2.createAnswer()) + .then((answer) => pc2.setLocalDescription(answer)) + .then(() => pc.setRemoteDescription(pc2.localDescription)) + .then(() => pc.createOffer(offerToReceiveFalse)) + .then((offer) => { + assert_equals(pc.getTransceivers().length, 1, + 'Expect pc to have one transceiver'); + + const transceiver = pc.getTransceivers()[0]; + assert_equals(transceiver.direction, 'sendonly', + 'Expect transceiver to have "sendonly" direction'); + }) + ; + }, `subsequent offerToReceive${capsKind} set to false with a track should change the direction to "sendonly"`); + }); + + promise_test(t => { + const pc = new RTCPeerConnection(); + + return pc.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true }) + .then(() => { + assert_equals(pc.getTransceivers().length, 2, + 'Expect pc to have two transceivers'); + + assert_equals(pc.getTransceivers()[0].direction, 'recvonly', + 'Expect first transceiver to have "recvonly" direction'); + assert_equals(pc.getTransceivers()[1].direction, 'recvonly', + 'Expect second transceiver to have "recvonly" direction'); + }); + }, 'offerToReceiveAudio and Video should create two "recvonly" transceivers'); + +</script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-getStats.https.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-getStats.https.html index a4df5630a3a..73138507d4f 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-getStats.https.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-getStats.https.html @@ -10,8 +10,8 @@ 'use strict'; // Test is based on the following editor draft: - // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html - // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html + // webrtc-pc 20171130 + // webrtc-stats 20171122 // The following helper function is called from RTCPeerConnection-helper.js // getTrackFromUserMedia @@ -20,39 +20,25 @@ // validateStatsReport // assert_stats_report_has_stats - /* - 8.2. RTCPeerConnection Interface Extensions - partial interface RTCPeerConnection { - Promise<RTCStatsReport> getStats(optional MediaStreamTrack? selector = null); - }; - - 8.3. RTCStatsReport Object - interface RTCStatsReport { - readonly maplike<DOMString, object>; - }; - - 8.4. RTCStats Dictionary - dictionary RTCStats { - DOMHighResTimeStamp timestamp; - RTCStatsType type; - DOMString id; - }; - - id - Two RTCStats objects, extracted from two different RTCStatsReport objects, MUST - have the same id if they were produced by inspecting the same underlying object. + // The following helper function is called from RTCPeerConnection-helper.js + // exchangeIceCandidates + // doSignalingHandshake + /* 8.2. getStats 1. Let selectorArg be the method's first argument. 2. Let connection be the RTCPeerConnection object on which the method was invoked. - 3. If selectorArg is neither null nor a valid MediaStreamTrack, return a promise - rejected with a newly created TypeError. + 3. If selectorArg is null, let selector be null. + 4. If selectorArg is a MediaStreamTrack let selector be an RTCRtpSender + or RTCRtpReceiver on connection which track member matches selectorArg. + If no such sender or receiver exists, or if more than one sender or + receiver fit this criteria, return a promise rejected with a newly + created InvalidAccessError. 5. Let p be a new promise. 6. Run the following steps in parallel: 1. Gather the stats indicated by selector according to the stats selection algorithm. 2. Resolve p with the resulting RTCStatsReport object, containing the gathered stats. */ - promise_test(() => { const pc = new RTCPeerConnection(); return pc.getStats(); @@ -65,9 +51,11 @@ /* 8.2. getStats - 4. Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track - member matches selectorArg. If no such sender or receiver exists, return a promise - rejected with a newly created InvalidAccessError. + 4. If selectorArg is a MediaStreamTrack let selector be an RTCRtpSender + or RTCRtpReceiver on connection which track member matches selectorArg. + If no such sender or receiver exists, or if more than one sender or + receiver fit this criteria, return a promise rejected with a newly + created InvalidAccessError. */ promise_test(t => { const pc = new RTCPeerConnection(); @@ -94,12 +82,6 @@ return pc.getStats(track); }, 'getStats() with track added via addTransceiver should succeed'); - /* - 8.2. getStats - 4. Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track - member matches selectorArg. If more than one sender or receiver fit this criteria, - return a promise rejected with a newly created InvalidAccessError. - */ promise_test(t => { const pc = new RTCPeerConnection(); return getTrackFromUserMedia('audio') @@ -140,7 +122,35 @@ validateStatsReport(statsReport); assert_stats_report_has_stats(statsReport, ['peer-connection']); }); - }, 'getStats() with no argument should return stats report containing peer-connection stats'); + }, 'getStats() with no argument should return stats report containing peer-connection stats on an empty PC'); + + promise_test(t => { + const pc = new RTCPeerConnection(); + return getTrackFromUserMedia('audio') + .then(([track, mediaStream]) => { + pc.addTrack(track, mediaStream); + return pc.getStats(); + }) + .then(statsReport => { + validateStatsReport(statsReport); + assert_stats_report_has_stats(statsReport, ['peer-connection']); + assert_stats_report_has_stats(statsReport, ['outbound-rtp']); + }); + }, 'getStats() with no argument should return stats report containing peer-connection stats and outbound-track-stats'); + + promise_test(t => { + const pc = new RTCPeerConnection(); + return getTrackFromUserMedia('audio') + .then(([track, mediaStream]) => { + pc.addTrack(track); + return pc.getStats(); + }) + .then(statsReport => { + validateStatsReport(statsReport); + assert_stats_report_has_stats(statsReport, ['peer-connection']); + assert_stats_report_has_stats(statsReport, ['outbound-rtp']); + }); + }, 'getStats() with no argument should return stats for no-stream tracks'); /* 8.5. The stats selection algorithm @@ -184,4 +194,150 @@ }); }, `getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats`); + /* + 8.6 Mandatory To Implement Stats + An implementation MUST support generating statistics of the following types + when the corresponding objects exist on a PeerConnection, with the attributes + that are listed when they are valid for that object. + */ + + const mandatoryStats = [ + "codec", + "inbound-rtp", + "outbound-rtp", + "remote-inbound-rtp", + "remote-outbound-rtp", + "peer-connection", + "data-channel", + "stream", + "track", + "transport", + "candidate-pair", + "local-candidate", + "remote-candidate", + "certificate" + ]; + + async_test(t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + + const dataChannel = pc1.createDataChannel('test-channel'); + + return navigator.mediaDevices.getUserMedia({ + audio: true, + video: true + }) + .then(t.step_func(mediaStream => { + const tracks = mediaStream.getTracks(); + assert_equals(tracks.length, 2, + 'Expect media stream to have one audio and one video track'); + + let audioTrack; + let videoTrack; + + for (const track of tracks) { + t.add_cleanup(() => track.stop()); + + pc1.addTrack(track, mediaStream); + + if (track.kind === 'audio') { + audioTrack = track; + } else if (track.kind === 'video') { + videoTrack = track; + } + } + + if (!audioTrack || ! videoTrack) { + assert_unreached('Expect mediaStream to have both audio and video streams'); + } + + const testStatsReport = (pc, statsReport) => { + validateStatsReport(statsReport); + assert_stats_report_has_stats(statsReport, mandatoryStats); + + const dataChannelStats = findStatsFromReport(statsReport, + stats => { + return stats.type === 'data-channel' && + stats.dataChannelIdentifier === dataChannel.id; + }, + 'Expect data channel stats to be found'); + + assert_equals(dataChannelStats.label, 'test-channel'); + + const audioTrackStats = findStatsFromReport(statsReport, + stats => { + return stats.type === 'track' && + stats.trackIdentifier === audioTrack.id; + }, + 'Expect audio track stats to be found'); + + assert_equals(audioTrackStats.kind, 'audio'); + + const videoTrackStats = findStatsFromReport(statsReport, + stats => { + return stats.type === 'track' && + stats.trackIdentifier === videoTrack.id; + }, + 'Expect video track stats to be found'); + + assert_equals(videoTrackStats.kind, 'video'); + + const mediaStreamStats = findStatsFromReport(statsReport, + stats => { + return stats.type === 'stream' && + stats.streamIdentifier === mediaStream.id; + }, + 'Expect media stream stats to be found'); + + assert_true(mediaStreamStats.trackIds.include(audioTrackStats.id)); + assert_true(mediaStreamStats.trackIds.include(videoTrackStats.id)); + } + + const onConnected = t.step_func(() => { + // Wait a while for the peer connections to collect stats + t.step_timeout(() => { + Promise.all([ + pc1.getStats() + .then(statsReport => testStatsReport(pc1, statsReport)), + + pc2.getStats() + .then(statsReport => testStatsReport(pc2, statsReport)) + ]) + .then(t.step_func_done()) + .catch(t.step_func(err => { + assert_unreached(`test failed with error: ${err}`); + })); + }, 200) + }) + + let onTrackCount = 0 + let onDataChannelCalled = false + + pc2.addEventListener('track', t.step_func(() => { + onTrackCount++; + if (onTrackCount === 2 && onDataChannelCalled) { + onConnected(); + } + })); + + pc2.addEventListener('datachannel', t.step_func(() => { + onDataChannelCalled = true; + if (onTrackCount === 2) { + onConnected(); + } + })); + + + exchangeIceCandidates(pc1, pc2); + doSignalingHandshake(pc1, pc2); + })) + .catch(t.step_func(err => { + assert_unreached(`test failed with error: ${err}`); + })); + + }, `getStats() with connected peer connections having tracks and data channel should return all mandatory to implement stats`); + </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-idl.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-idl.html deleted file mode 100644 index 58a153f2946..00000000000 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-idl.html +++ /dev/null @@ -1,108 +0,0 @@ -<!doctype html> -<html> -<head> -<meta charset=utf-8> -<title>IDL check of RTCPeerConnection</title> -<link rel="author" title="Harald Alvestrand" href="mailto:hta@google.com"/> -<link rel="help" href="http://w3c.github.io/webrtc-pc/#rtcpeerconnection-interface"> -</head> -<body> - -<h1 class="instructions">Description</h1> -<p class="instructions">This test verifies the availability of the RTCPeerConnection interface.</p> -<div id='log'></div> -<script src=/resources/testharness.js></script> -<script src=/resources/testharnessreport.js></script> -<script src=/resources/WebIDLParser.js></script> -<script src=/resources/idlharness.js></script> - -<!-- The IDL is copied from the 22 September 2015 editors' draft. --> -<script type="text/plain"> -[Constructor()] -interface EventTarget { - // Only a dummy definition is needed here. -}; -[ Constructor (optional RTCConfiguration configuration)] -interface RTCPeerConnection : EventTarget { - Promise<RTCSessionDescription> createOffer (optional RTCOfferOptions options); - Promise<RTCSessionDescription> createAnswer (optional RTCAnswerOptions options); - Promise<void> setLocalDescription (RTCSessionDescription description); - readonly attribute RTCSessionDescription? localDescription; - readonly attribute RTCSessionDescription? currentLocalDescription; - readonly attribute RTCSessionDescription? pendingLocalDescription; - Promise<void> setRemoteDescription (RTCSessionDescription description); - readonly attribute RTCSessionDescription? remoteDescription; - readonly attribute RTCSessionDescription? currentRemoteDescription; - readonly attribute RTCSessionDescription? pendingRemoteDescription; - Promise<void> addIceCandidate (RTCIceCandidate candidate); - readonly attribute RTCSignalingState signalingState; - readonly attribute RTCIceGatheringState iceGatheringState; - readonly attribute RTCIceConnectionState iceConnectionState; - readonly attribute boolean? canTrickleIceCandidates; - RTCConfiguration getConfiguration (); - void setConfiguration (RTCConfiguration configuration); - void close (); - attribute EventHandler onnegotiationneeded; - attribute EventHandler onicecandidate; - attribute EventHandler onsignalingstatechange; - attribute EventHandler oniceconnectionstatechange; - attribute EventHandler onicegatheringstatechange; -}; - -partial interface RTCPeerConnection { - void createOffer (RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback, optional RTCOfferOptions options); - void setLocalDescription (RTCSessionDescription description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback); - void createAnswer (RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback); - void setRemoteDescription (RTCSessionDescription description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback); - void addIceCandidate (RTCIceCandidate candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback); - void getStats (MediaStreamTrack? selector, RTCStatsCallback successCallback, RTCPeerConnectionErrorCallback failureCallback); -}; - -partial interface RTCPeerConnection { - static Promise<RTCCertificate> generateCertificate (AlgorithmIdentifier keygenAlgorithm); -}; - -partial interface RTCPeerConnection { - sequence<RTCRtpSender> getSenders (); - sequence<RTCRtpReceiver> getReceivers (); - RTCRtpSender addTrack (MediaStreamTrack track, MediaStream... streams); - void removeTrack (RTCRtpSender sender); - attribute EventHandler ontrack; -}; - -partial interface RTCPeerConnection { - RTCDataChannel createDataChannel ([TreatNullAs=EmptyString] DOMString label, optional RTCDataChannelInit dataChannelDict); - attribute EventHandler ondatachannel; -}; - -partial interface RTCPeerConnection { - readonly attribute RTCDTMFSender? dtmf; -}; - -partial interface RTCPeerConnection { - Promise<RTCStatsReport> getStats (optional MediaStreamTrack? selector); -}; - -partial interface RTCPeerConnection { - void setIdentityProvider (DOMString provider, optional DOMString protocol, optional DOMString usernameHint); - Promise<DOMString> getIdentityAssertion (); - readonly attribute Promise<RTCIdentityAssertion> peerIdentity; - readonly attribute DOMString? idpLoginUrl; -}; - -</script> -<script> -(function() { - var idl_array = new IdlArray(); - [].forEach.call(document.querySelectorAll("script[type=text\\/plain]"), - function(node) { - idl_array.add_idls(node.textContent); - }); - window.pc = new RTCPeerConnection(null); - idl_array.add_objects({"RTCPeerConnection": ["pc"]}); - idl_array.test(); - done(); -})(); -</script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-peerIdentity.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-peerIdentity.html index 845c55dc6d2..d5f9db9d5d0 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-peerIdentity.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-peerIdentity.html @@ -284,7 +284,7 @@ pc2.setRemoteDescription(offer)) .then(() => assert_rtcerror_rejection('idp-execution-failure', - peerIdentityPromise, + peerIdentityPromise1, `Expect first peerIdentity promise to be rejected with RTCError('idp-execution-failure')`)) .then(() => { const peerIdentityPromise2 = pc2.peerIdentity; diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html index f530e94cfe0..d4663e5373b 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html @@ -24,11 +24,11 @@ const callee = new RTCPeerConnection(); return getUserMediaTracksAndStreams(1) .then(t.step_func(([tracks, streams]) => { - let localTrack = tracks[0]; + const localTrack = tracks[0]; caller.addTrack(localTrack); - let offerPromise = performOffer(caller, callee); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { - let remoteTrack = trackEvent.track; + const remoteTrack = trackEvent.track; assert_equals(remoteTrack.id, localTrack.id, 'Expected local and remote track IDs to match.'); assert_equals(trackEvent.streams.length, 0, @@ -47,15 +47,15 @@ const callee = new RTCPeerConnection(); return getUserMediaTracksAndStreams(1) .then(t.step_func(([tracks, streams]) => { - let localTrack = tracks[0]; - let localStream = streams[0]; + const localTrack = tracks[0]; + const localStream = streams[0]; caller.addTrack(localTrack, localStream); - let offerPromise = performOffer(caller, callee); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { assert_equals(trackEvent.streams.length, 1, 'Expected track event to fire with a single stream.'); - let remoteTrack = trackEvent.track; - let remoteStream = trackEvent.streams[0]; + const remoteTrack = trackEvent.track; + const remoteStream = trackEvent.streams[0]; assert_equals(remoteTrack.id, localTrack.id, 'Expected local and remote track IDs to match.'); assert_equals(remoteStream.id, localStream.id, @@ -74,19 +74,47 @@ async_test(t => { const caller = new RTCPeerConnection(); const callee = new RTCPeerConnection(); + let eventSequence = ''; + return getUserMediaTracksAndStreams(1) + .then(t.step_func(([tracks, streams]) => { + const ontrackResolver = new Resolver(); + callee.ontrack = () => { + eventSequence += 'ontrack;'; + ontrackResolver.resolve(); + } + caller.addTrack(tracks[0]); + return Promise.all([ + ontrackResolver.promise, + performOffer(caller, callee).then(() => { + eventSequence += 'setRemoteDescription;'; + }) + ]); + })) + .then(t.step_func(() => { + assert_equals(eventSequence, 'ontrack;setRemoteDescription;'); + t.done(); + })) + .catch(t.step_func(reason => { + assert_unreached(reason); + })); + }, 'ontrack fires before setRemoteDescription resolves.'); + + async_test(t => { + const caller = new RTCPeerConnection(); + const callee = new RTCPeerConnection(); return getUserMediaTracksAndStreams(2) .then(t.step_func(([tracks, streams]) => { - let localTrack1 = tracks[0]; - let localTrack2 = tracks[1]; - let localStream = streams[0]; + const localTrack1 = tracks[0]; + const localTrack2 = tracks[1]; + const localStream = streams[0]; caller.addTrack(localTrack1, localStream); caller.addTrack(localTrack2, localStream); - let offerPromise = performOffer(caller, callee); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { assert_equals(trackEvent.streams.length, 1, 'Expected track event to fire with a single stream.'); - let remoteTrack1 = trackEvent.track; - let remoteStream = trackEvent.streams[0]; + const remoteTrack1 = trackEvent.track; + const remoteStream = trackEvent.streams[0]; assert_equals(remoteTrack1.id, localTrack1.id, 'Expected first remote track ID to match first local track ID.'); assert_equals(remoteStream.getTracks().length, 2, @@ -94,7 +122,7 @@ callee.ontrack = t.step_func(trackEvent => { assert_equals(trackEvent.streams.length, 1, 'Expected track event to fire with a single stream.'); - let remoteTrack2 = trackEvent.track; + const remoteTrack2 = trackEvent.track; assert_equals(trackEvent.streams[0], remoteStream, 'Expected both track events to fire with the same remote stream.'); assert_equals(remoteTrack2.id, localTrack2.id, @@ -151,17 +179,53 @@ async_test(t => { const caller = new RTCPeerConnection(); const callee = new RTCPeerConnection(); + let eventSequence = ''; + return getUserMediaTracksAndStreams(2) + .then(t.step_func(([tracks, streams]) => { + const localTracks = tracks; + const localStream = streams[0]; + caller.addTrack(localTracks[0], localStream); + const offerPromise = performOffer(caller, callee); + callee.ontrack = t.step_func(trackEvent => { + callee.ontrack = null; + const remoteStream = trackEvent.streams[0]; + const onaddtrackResolver = new Resolver(); + remoteStream.onaddtrack = () => { + eventSequence += 'stream.onaddtrack;'; + onaddtrackResolver.resolve(); + } + caller.addTrack(localTracks[1], localStream); + Promise.all([ + onaddtrackResolver.promise, + performOffer(caller, callee).then(() => { + eventSequence += 'setRemoteDescription;'; + }) + ]).then(t.step_func(() => { + assert_equals(eventSequence, 'stream.onaddtrack;setRemoteDescription;'); + t.done(); + })); + }); + return offerPromise; + })) + .catch(t.step_func(reason => { + assert_unreached(reason); + })); + }, 'stream.onaddtrack fires before setRemoteDescription resolves.'); + + async_test(t => { + const caller = new RTCPeerConnection(); + const callee = new RTCPeerConnection(); return getUserMediaTracksAndStreams(2) .then(t.step_func(([tracks, streams]) => { - let localTrack = tracks[0]; - let localStreams = streams; + const localTrack = tracks[0]; + const localStreams = streams; caller.addTrack(localTrack, localStreams[0], localStreams[1]); - let performOffer = performOffer(caller, callee); + const performOffer = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { assert_equals(trackEvent.streams.length, 2, 'Expected the track event to fire with two streams.'); - let remoteTrack = trackEvent.track; - let remoteStreams = trackEvent.streams; + const remoteTrack = trackEvent.track; + const remoteStreams = trackEvent.streams; assert_equals(remoteTrack.id, localTrack.id, 'Expected local and remote track IDs to match.'); assert_equals(remoteStreams[0].id, localStreams[0].id, @@ -184,38 +248,10 @@ async_test(t => { const caller = new RTCPeerConnection(); const callee = new RTCPeerConnection(); - let eventSequence = ''; return getUserMediaTracksAndStreams(1) .then(t.step_func(([tracks, streams]) => { caller.addTrack(tracks[0]); - let ontrackResolver = new Resolver(); - callee.ontrack = () => { - eventSequence += 'ontrack;'; - ontrackResolver.resolve(); - } - return Promise.all([ - ontrackResolver.promise, - performOffer(caller, callee).then(() => { - eventSequence += 'setRemoteDescription;'; - }) - ]); - })) - .then(t.step_func(() => { - assert_equals(eventSequence, 'ontrack;setRemoteDescription;'); - t.done(); - })) - .catch(t.step_func(reason => { - assert_unreached(reason); - })); - }, 'ontrack fires before setRemoteDescription resolves.'); - - async_test(t => { - const caller = new RTCPeerConnection(); - const callee = new RTCPeerConnection(); - return getUserMediaTracksAndStreams(1) - .then(t.step_func(([tracks, streams]) => { - caller.addTrack(tracks[0]); - let offerPromise = performOffer(caller, callee); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { assert_array_equals(callee.getReceivers(), [trackEvent.receiver]); t.done(); @@ -232,11 +268,11 @@ const callee = new RTCPeerConnection(); return getUserMediaTracksAndStreams(1) .then(t.step_func(([tracks, streams]) => { - let sender = caller.addTrack(tracks[0]); - assert_true(sender != null); - let offerPromise = performOffer(caller, callee); + const sender = caller.addTrack(tracks[0]); + assert_not_equals(sender, null); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { - let receivers = callee.getReceivers(); + const receivers = callee.getReceivers(); assert_equals(receivers.length, 1, 'Expected getReceivers() to be the track event\'s receiver.'); caller.removeTrack(sender); @@ -259,9 +295,9 @@ const callee = new RTCPeerConnection(); return getUserMediaTracksAndStreams(1) .then(t.step_func(([tracks, streams]) => { - let sender = caller.addTrack(tracks[0], streams[0]); - assert_true(sender != null); - let offerPromise = performOffer(caller, callee); + const sender = caller.addTrack(tracks[0], streams[0]); + assert_not_equals(sender, null); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { assert_not_equals(trackEvent.track, null); assert_equals(trackEvent.streams.length, 1); @@ -279,21 +315,55 @@ .catch(t.step_func(reason => { assert_unreached(reason); })); - }, 'removeTrack() causes onremovetrack and the track to be removed from the stream.'); + }, 'removeTrack() makes stream.onremovetrack fire and the track to be removed from the stream.'); async_test(t => { const caller = new RTCPeerConnection(); const callee = new RTCPeerConnection(); + let eventSequence = ''; return getUserMediaTracksAndStreams(1) .then(t.step_func(([tracks, streams]) => { - let sender = caller.addTrack(tracks[0]); - assert_true(sender != null); - let offerPromise = performOffer(caller, callee); + const sender = caller.addTrack(tracks[0], streams[0]); + assert_not_equals(sender, null); + const offerPromise = performOffer(caller, callee); callee.ontrack = t.step_func(trackEvent => { - assert_not_equals(trackEvent.track, null); + const remoteStream = trackEvent.streams[0]; + const onremovetrackResolver = new Resolver(); + remoteStream.onremovetrack = t.step_func(removeEvent => { + eventSequence += 'stream.onremovetrack;'; + onremovetrackResolver.resolve(); + }); + caller.removeTrack(sender); + return Promise.all([ + onremovetrackResolver.promise, + performOffer(caller, callee).then(() => { + eventSequence += 'setRemoteDescription;'; + }) + ]).then(t.step_func(() => { + assert_equals(eventSequence, 'stream.onremovetrack;setRemoteDescription;'); + t.done(); + })); + }); + return offerPromise; + })) + .catch(t.step_func(reason => { + assert_unreached(reason); + })); + }, 'stream.onremovetrack fires before setRemoteDescription resolves.'); + + async_test(t => { + const caller = new RTCPeerConnection(); + const callee = new RTCPeerConnection(); + return getUserMediaTracksAndStreams(1) + .then(t.step_func(([tracks, streams]) => { + const sender = caller.addTrack(tracks[0]); + assert_not_equals(sender, null); + const offerPromise = performOffer(caller, callee); + callee.ontrack = t.step_func(trackEvent => { + const remoteTrack = trackEvent.track; caller.removeTrack(sender); performOffer(caller, callee); - trackEvent.track.onmute = t.step_func(() => { + remoteTrack.onmute = t.step_func(() => { assert_true(trackEvent.track.muted); t.done(); }); @@ -303,6 +373,54 @@ .catch(t.step_func(reason => { assert_unreached(reason); })); - }, 'removeTrack() causes onmute and the track to be muted.'); + }, 'removeTrack() makes track.onmute fire and the track to be muted.'); + async_test(t => { + const caller = new RTCPeerConnection(); + const callee = new RTCPeerConnection(); + let eventSequence = ''; + return getUserMediaTracksAndStreams(1) + .then(t.step_func(([tracks, streams]) => { + const sender = caller.addTrack(tracks[0]); + assert_not_equals(sender, null); + const offerPromise = performOffer(caller, callee); + callee.ontrack = t.step_func(trackEvent => { + const remoteTrack = trackEvent.track; + const onmuteResolver = new Resolver(); + remoteTrack.onmute = t.step_func(() => { + eventSequence += 'track.onmute;'; + onmuteResolver.resolve(); + }); + caller.removeTrack(sender); + return Promise.all([ + onmuteResolver.promise, + performOffer(caller, callee).then(() => { + eventSequence += 'setRemoteDescription;'; + }) + ]).then(t.step_func(() => { + assert_equals(eventSequence, 'track.onmute;setRemoteDescription;'); + t.done(); + })); + }); + return offerPromise; + })) + .catch(t.step_func(reason => { + assert_unreached(reason); + })); + }, 'track.onmute fires before setRemoteDescription resolves.'); + + async_test(t => { + const pc = new RTCPeerConnection(); + return getUserMediaTracksAndStreams(1) + .then(t.step_func(([tracks, streams]) => { + const sender = pc.addTrack(tracks[0]); + assert_not_equals(sender, null); + pc.removeTrack(sender); + pc.removeTrack(sender); + t.done(); + })) + .catch(t.step_func(reason => { + assert_unreached(reason); + })); + }, 'removeTrack() twice is safe.'); </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCRtpCapabilities-helper.js b/tests/wpt/web-platform-tests/webrtc/RTCRtpCapabilities-helper.js index 8617e7f4f97..72f544059ff 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCRtpCapabilities-helper.js +++ b/tests/wpt/web-platform-tests/webrtc/RTCRtpCapabilities-helper.js @@ -48,5 +48,5 @@ function validateCodecCapability(codec) { } function validateHeaderExtensionCapability(headerExt) { - assert_optional_string_field(uri); + assert_optional_string_field(headerExt, 'uri'); } diff --git a/tests/wpt/web-platform-tests/webrtc/RTCRtpSender-getStats.html b/tests/wpt/web-platform-tests/webrtc/RTCRtpSender-getStats.https.html index 00aa680f53e..e5cb1eb99fa 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCRtpSender-getStats.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCRtpSender-getStats.https.html @@ -9,8 +9,8 @@ 'use strict'; // Test is based on the following editor draft: - // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html - // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html + // webrtc-pc 20171130 + // webrtc-stats 20171122 // The following helper function is called from RTCStats-helper.js // validateStatsReport @@ -18,11 +18,6 @@ /* 5.2. RTCRtpSender Interface - interface RTCRtpSender { - Promise<RTCStatsReport> getStats(); - ... - }; - getStats 1. Let selector be the RTCRtpSender object on which the method was invoked. 2. Let p be a new promise, and run the following steps in parallel: @@ -49,6 +44,22 @@ validateStatsReport(statsReport); assert_stats_report_has_stats(statsReport, ['outbound-rtp']); }); - }, 'sender.getStats() should return stats report containing outbound-rtp stats'); + }, 'sender.getStats() via addTransceiver should return stats report containing outbound-rtp stats'); + + promise_test(() => { + const pc = new RTCPeerConnection(); + + return navigator.mediaDevices.getUserMedia({ audio: true }) + .then(mediaStream => { + const [track] = mediaStream.getTracks(); + const sender = pc.addTrack(track, mediaStream); + + return sender.getStats() + .then(statsReport => { + validateStatsReport(statsReport); + assert_stats_report_has_stats(statsReport, ['outbound-rtp']); + }); + }) + }, 'sender.getStats() via addTrack should return stats report containing outbound-rtp stats'); </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCSctpTransport-maxMessageSize.html b/tests/wpt/web-platform-tests/webrtc/RTCSctpTransport-maxMessageSize.html new file mode 100644 index 00000000000..7d73cc5a3b5 --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/RTCSctpTransport-maxMessageSize.html @@ -0,0 +1,181 @@ +<!doctype html> +<meta charset=utf-8> +<title>RTCSctpTransport.prototype.maxMessageSize</title> +<link rel="help" href="https://w3c.github.io/webrtc-pc/#rtcsctptransport-interface"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="RTCPeerConnection-helper.js"></script> +<script> +'use strict'; + +// This test has an assert_unreached() that requires that the variable +// canSendSize (initiated below) is greater than 2, if non-zero. The reason +// is that we need two non-zero values that are less that that value for +// testing with predictable results. This is a bit unfortunate but shouldn't +// have any practical impact. + +// Helper class to read SDP attributes and generate SDPs with modified attribute values +class SDPAttributeHelper { + constructor(attrName, valueRegExpStr) { + this.attrName = attrName; + this.re = new RegExp(`^a=${attrName}:(${valueRegExpStr})\\r\\n`, 'm'); + } + + getValue(sdp) { + const matches = sdp.match(this.re); + return matches ? matches[1] : null; + } + + sdpWithValue(sdp, value) { + const matches = sdp.match(this.re); + const sdpParts = sdp.split(matches[0]); + const attributeLine = arguments.length > 1 ? `a=${this.attrName}:${value}\r\n` : ''; + return `${sdpParts[0]}${attributeLine}${sdpParts[1]}`; + } + + sdpWithoutAttribute(sdp) { + return this.sdpWithValue(sdp); + } +} + +const mmsAttributeHelper = new SDPAttributeHelper('max-message-size', '\\d+'); +let canSendSize; +const remoteValue1 = 1; +const remoteValue2 = 2; + +promise_test(t => { + const pc = new RTCPeerConnection(); + assert_equals(pc.sctp, null); + let maxMessageSize; + + return generateOffer({ pc, data: true }) + .then((offer) => { + assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null, + 'SDP should have max-message-size attribute'); + + offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, 0) }; + return pc.setRemoteDescription(offer); + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => { + assert_not_equals(pc.sctp, null); + canSendSize = pc.sctp.maxMessageSize == Number.POSITIVE_INFINITY ? 0 : pc.sctp.maxMessageSize; + if (canSendSize != 0 && canSendSize < remoteValue2) { + assert_unreached('This test needs two values that are less than canSendSize (unless it is zero)'); + } + }); +}, 'Determine the local side send limitation (canSendSize) by offering a max-message-size of 0'); + +promise_test(t => { + const pc = new RTCPeerConnection(); + assert_equals(pc.sctp, null); + + return generateOffer({ pc, data: true }) + .then((offer) => { + assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null, + 'SDP should have max-message-size attribute'); + + // Remove the max-message-size SDP attribute + offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithoutAttribute(offer.sdp) }; + return pc.setRemoteDescription(offer) + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => { + assert_not_equals(pc.sctp, null); + // Test outcome depends on canSendSize value + if (canSendSize) { + assert_equals(pc.sctp.maxMessageSize, Math.min(65535, canSendSize), + 'Missing SDP attribute and a non-zero canSendSize should give an maxMessageSize of min(65535, canSendSize)'); + } else { + assert_equals(pc.sctp.maxMessageSize, 65535, + 'Missing SDP attribute and a canSendSize of 0 should give an maxMessageSize of 65535'); + } + }); +}, 'Remote offer SDP missing max-message-size attribute'); + +promise_test(t => { + const pc = new RTCPeerConnection(); + assert_equals(pc.sctp, null); + + return generateOffer({ pc, data: true }) + .then((offer) => { + assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null, + 'SDP should have max-message-size attribute'); + + offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, remoteValue1) }; + return pc.setRemoteDescription(offer); + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => { + assert_not_equals(pc.sctp, null); + assert_equals(pc.sctp.maxMessageSize, remoteValue1, + 'maxMessageSize should be the value provided by the remote peer (as long as it is less than canSendSize)'); + }); +}, 'max-message-size with a (non-zero) value provided by the remote peer'); + +promise_test(t => { + const pc = new RTCPeerConnection(); + assert_equals(pc.sctp, null); + + return generateOffer({ pc, data: true }) + .then((offer) => { + assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null, + 'SDP should have max-message-size attribute'); + + offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, remoteValue1) }; + return pc.setRemoteDescription(offer) + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => { + assert_not_equals(pc.sctp, null); + assert_equals(pc.sctp.maxMessageSize, remoteValue1, + 'maxMessageSize should be the value provided by the remote peer (as long as it is less than canSendSize)'); + }) + .then(() => pc.createOffer()) // Start new O/A exchange that updates max-message-size + .then((offer) => { + offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, remoteValue2)}; + return pc.setRemoteDescription(offer) + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => { + assert_not_equals(pc.sctp, null); + assert_equals(pc.sctp.maxMessageSize, remoteValue2, + 'maxMessageSize should be the new value provided by the remote peer (as long as it is less than canSendSize)'); + }) + ; +}, 'Renegotiate max-message-size with a (non-zero) value provided by the remote peer'); + +promise_test(t => { + const pc = new RTCPeerConnection(); + assert_equals(pc.sctp, null); + const largerThanCanSendSize = canSendSize + 1; + + return generateOffer({ pc, data: true }) + .then((offer) => { + assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null, + 'SDP should have max-message-size attribute'); + + offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, largerThanCanSendSize) }; + return pc.setRemoteDescription(offer) + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => { + assert_not_equals(pc.sctp, null); + // Test outcome depends on canSendSize value + if (canSendSize) { + assert_equals(pc.sctp.maxMessageSize, canSendSize, + 'A remote value larger than a non-zero canSendSize should limit maxMessageSize to canSendSize'); + } else { + assert_equals(pc.sctp.maxMessageSize, 65535, + 'A canSendSize of zero should let the remote value set maxMessageSize'); + } + }); +}, 'max-message-size with a (non-zero) value larger than canSendSize provided by the remote peer'); + +</script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCStats-helper.js b/tests/wpt/web-platform-tests/webrtc/RTCStats-helper.js index 1ff3ae6711b..c2be1b3124f 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCStats-helper.js +++ b/tests/wpt/web-platform-tests/webrtc/RTCStats-helper.js @@ -1,18 +1,12 @@ 'use strict'; // Test is based on the following editor draft: -// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html -// https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html - +// webrtc-pc 20171130 +// webrtc-stats 20171122 // This file depends on dictionary-helper.js which should // be loaded from the main HTML file. -// To improve readability, the WebIDL definitions of the Stats -// dictionaries are modified to annotate with required fields when -// they are required by section 8.6 of webrtc-pc. ID fields are -// also annotated with the stats type that they are linked to. - /* [webrtc-stats] 6.1. RTCStatsType enum @@ -82,6 +76,16 @@ function assert_stats_report_has_stats(statsReport, statsTypes) { } } +function findStatsFromReport(statsReport, predicate, message) { + for (const stats of statsReport.values()) { + if (predicate(stats)) { + return stats; + } + } + + assert_unreached(message || 'none of stats in statsReport satisfy given condition') +} + // Get stats object of type that is expected to be // found in the statsReport function getRequiredStats(statsReport, type) { @@ -132,7 +136,7 @@ function validateOptionalIdField(statsReport, stats, field, type) { }; */ function validateRtcStats(statsReport, stats) { - assert_number_field(stats, 'timeStamp'); + assert_number_field(stats, 'timestamp'); assert_string_field(stats, 'type'); assert_string_field(stats, 'id'); } @@ -141,35 +145,32 @@ function validateRtcStats(statsReport, stats) { [webrtc-stats] 7.1. RTCRTPStreamStats dictionary dictionary RTCRTPStreamStats : RTCStats { - required unsigned long ssrc; - required DOMString mediaType; - - [RTCMediaStreamTrackStats] - required DOMString trackId; - - [RTCTransportStats] - required DOMString transportId; - - [RTCCodecStats] - required DOMString codecId; - - unsigned long firCount; - unsigned long pliCount; - required unsigned long nackCount; - unsigned long sliCount; - unsigned long long qpSum; + unsigned long ssrc; + DOMString mediaType; + DOMString trackId; + DOMString transportId; + DOMString codecId; + unsigned long firCount; + unsigned long pliCount; + unsigned long nackCount; + unsigned long sliCount; + unsigned long long qpSum; }; + mediaType of type DOMString + Either "audio" or "video". + [webrtc-pc] 8.6. Mandatory To Implement Stats - - RTCRTPStreamStats, with attributes ssrc, associateStatsId, isRemote, mediaType, - mediaTrackId, transportId, codecId, nackCount + - RTCRTPStreamStats, with attributes ssrc, mediaType, trackId, + transportId, codecId, nackCount */ function validateRtpStreamStats(statsReport, stats) { validateRtcStats(statsReport, stats); assert_unsigned_int_field(stats, 'ssrc'); assert_string_field(stats, 'mediaType'); + assert_enum_field(stats, 'mediaType', ['audio', 'video']) validateIdField(statsReport, stats, 'trackId', 'track'); validateIdField(statsReport, stats, 'transportId', 'transport'); @@ -186,15 +187,12 @@ function validateRtpStreamStats(statsReport, stats) { [webrtc-stats] 7.2. RTCCodecStats dictionary dictionary RTCCodecStats : RTCStats { - required unsigned long payloadType; - required RTCCodecType codecType; - - [RTCTransportStats] + unsigned long payloadType; + RTCCodecType codecType; DOMString transportId; - DOMString mimeType; - required unsigned long clockRate; - required unsigned long channels; + unsigned long clockRate; + unsigned long channels; DOMString sdpFmtpLine; DOMString implementation; }; @@ -206,7 +204,7 @@ function validateRtpStreamStats(statsReport, stats) { [webrtc-pc] 8.6. Mandatory To Implement Stats - - RTCCodecStats, with attributes payloadType, codec, clockRate, channels, parameters + - RTCCodecStats, with attributes payloadType, codec, clockRate, channels, sdpFmtpLine */ function validateCodecStats(statsReport, stats) { @@ -221,7 +219,7 @@ function validateCodecStats(statsReport, stats) { assert_unsigned_int_field(stats, 'clockRate'); assert_unsigned_int_field(stats, 'channels'); - assert_optional_string_field(stats, 'sdpFmtpLine'); + assert_string_field(stats, 'sdpFmtpLine'); assert_optional_string_field(stats, 'implementation'); } @@ -229,34 +227,42 @@ function validateCodecStats(statsReport, stats) { [webrtc-stats] 7.3. RTCReceivedRTPStreamStats dictionary dictionary RTCReceivedRTPStreamStats : RTCRTPStreamStats { - unsigned long packetsReceived; - unsigned long long bytesReceived; - unsigned long packetsLost; - double jitter; - double fractionLost; - unsigned long packetsDiscarded; - unsigned long packetsRepaired; - unsigned long burstPacketsLost; - unsigned long burstPacketsDiscarded; - unsigned long burstLossCount; - unsigned long burstDiscardCount; - double burstLossRate; - double burstDiscardRate; - double gapLossRate; - double gapDiscardRate; + unsigned long packetsReceived; + unsigned long long bytesReceived; + long packetsLost; + double jitter; + double fractionLost; + unsigned long packetsDiscarded; + unsigned long packetsFailedDecryption; + unsigned long packetsRepaired; + unsigned long burstPacketsLost; + unsigned long burstPacketsDiscarded; + unsigned long burstLossCount; + unsigned long burstDiscardCount; + double burstLossRate; + double burstDiscardRate; + double gapLossRate; + double gapDiscardRate; }; + + [webrtc-pc] + 8.6. Mandatory To Implement Stats + - RTCReceivedRTPStreamStats, with all required attributes from its + inherited dictionaries, and also attributes packetsReceived, + bytesReceived, packetsLost, jitter, packetsDiscarded */ function validateReceivedRtpStreamStats(statsReport, stats) { validateRtpStreamStats(statsReport, stats); - assert_optional_unsigned_int_field(stats, 'packetsReceived'); - assert_optional_unsigned_int_field(stats, 'bytesReceived'); - assert_optional_unsigned_int_field(stats, 'packetsLost'); + assert_unsigned_int_field(stats, 'packetsReceived'); + assert_unsigned_int_field(stats, 'bytesReceived'); + assert_unsigned_int_field(stats, 'packetsLost'); - assert_optional_number_field(stats, 'jitter'); + assert_number_field(stats, 'jitter'); assert_optional_number_field(stats, 'fractionLost'); - assert_optional_unsigned_int_field(stats, 'packetsDiscarded'); + assert_unsigned_int_field(stats, 'packetsDiscarded'); + assert_optional_unsigned_int_field(stats, 'packetsFailedDecryption'); assert_optional_unsigned_int_field(stats, 'packetsRepaired'); assert_optional_unsigned_int_field(stats, 'burstPacketsLost'); assert_optional_unsigned_int_field(stats, 'burstPacketsDiscarded'); @@ -273,37 +279,21 @@ function validateReceivedRtpStreamStats(statsReport, stats) { [webrtc-stats] 7.4. RTCInboundRTPStreamStats dictionary dictionary RTCInboundRTPStreamStats : RTCReceivedRTPStreamStats { - required unsigned long packetsReceived; - required unsigned long long bytesReceived; - required unsigned long packetsLost; - required double jitter; - required unsigned long packetsDiscarded; - - [RTCRemoteOutboundRTPStreamStats] DOMString remoteId; - unsigned long framesDecoded; DOMHighResTimeStamp lastPacketReceivedTimestamp; }; [webrtc-pc] 8.6. Mandatory To Implement Stats - - RTCInboundRTPStreamStats, with all required attributes from RTCRTPStreamStats, - and also attributes packetsReceived, bytesReceived, packetsLost, jitter, - packetsDiscarded + - RTCInboundRTPStreamStats, with all required attributes from its inherited + dictionaries, and also attributes remoteId, framesDecoded */ function validateInboundRtpStreamStats(statsReport, stats) { validateReceivedRtpStreamStats(statsReport, stats); - assert_unsigned_int_field(stats, 'packetsReceived'); - assert_unsigned_int_field(stats, 'bytesReceived'); - assert_unsigned_int_field(stats, 'packetsLost'); - assert_number_field(stats, 'jitter'); - assert_unsigned_int_field(stats, 'packetsDiscarded'); - - validateOptionalIdField(statsReport, stats, 'remoteId', 'remote-outbound-rtp'); - - assert_optional_unsigned_int_field(stats, 'framesDecoded'); + validateIdField(statsReport, stats, 'remoteId', 'remote-outbound-rtp'); + assert_unsigned_int_field(stats, 'framesDecoded'); assert_optional_number_field(stats, 'lastPacketReceivedTimeStamp'); } @@ -311,18 +301,20 @@ function validateInboundRtpStreamStats(statsReport, stats) { [webrtc-stats] 7.5. RTCRemoteInboundRTPStreamStats dictionary dictionary RTCRemoteInboundRTPStreamStats : RTCReceivedRTPStreamStats { - [RTCOutboundRTPStreamStats] - DOMString localId; - - double roundTripTime; + DOMString localId; + double roundTripTime; }; - */ + [webrtc-pc] + 8.6. Mandatory To Implement Stats + - RTCRemoteInboundRTPStreamStats, with all required attributes from its + inherited dictionaries, and also attributes localId, roundTripTime + */ function validateRemoteInboundRtpStreamStats(statsReport, stats) { validateReceivedRtpStreamStats(statsReport, stats); - validateOptionalIdField(statsReport, stats, 'localId', 'outbound-rtp'); - assert_optional_number_field(stats, 'roundTripTime'); + validateIdField(statsReport, stats, 'localId', 'outbound-rtp'); + assert_number_field(stats, 'roundTripTime'); } /* @@ -334,13 +326,18 @@ function validateRemoteInboundRtpStreamStats(statsReport, stats) { unsigned long long bytesSent; unsigned long long bytesDiscardedOnSend; }; + + [webrtc-pc] + 8.6. Mandatory To Implement Stats + - RTCSentRTPStreamStats, with all required attributes from its inherited + dictionaries, and also attributes packetsSent, bytesSent */ function validateSentRtpStreamStats(statsReport, stats) { validateRtpStreamStats(statsReport, stats); - assert_optional_unsigned_int_field(stats, 'packetsSent'); + assert_unsigned_int_field(stats, 'packetsSent'); assert_optional_unsigned_int_field(stats, 'packetsDiscardedOnSend'); - assert_optional_unsigned_int_field(stats, 'bytesSent'); + assert_unsigned_int_field(stats, 'bytesSent'); assert_optional_unsigned_int_field(stats, 'bytesDiscardedOnSend'); } @@ -348,12 +345,7 @@ function validateSentRtpStreamStats(statsReport, stats) { [webrtc-stats] 7.7. RTCOutboundRTPStreamStats dictionary dictionary RTCOutboundRTPStreamStats : RTCSentRTPStreamStats { - required unsigned long packetsSent; - required unsigned long long bytesSent; - - [RTCRemoteInboundRTPStreamStats] DOMString remoteId; - DOMHighResTimeStamp lastPacketSentTimestamp; double targetBitrate; unsigned long framesEncoded; @@ -361,20 +353,19 @@ function validateSentRtpStreamStats(statsReport, stats) { double averageRTCPInterval; }; - [webrtc-pc] - 8.6. Mandatory To Implement Stats - - RTCOutboundRTPStreamStats, with all required attributes from RTCRTPStreamStats, - and also attributes packetsSent, bytesSent, roundTripTime + [webrtc-pc] + 8.6. Mandatory To Implement Stats + - RTCOutboundRTPStreamStats, with all required attributes from its + inherited dictionaries, and also attributes remoteId, framesEncoded */ function validateOutboundRtpStreamStats(statsReport, stats) { - validateOptionalIdField(statsReport, stats, 'remoteId', 'remote-inbound-rtp'); + validateSentRtpStreamStats(statsReport, stats) - assert_unsigned_int_field(stats, 'packetsSent'); - assert_unsigned_int_field(stats, 'bytesSent'); + validateIdField(statsReport, stats, 'remoteId', 'remote-inbound-rtp'); assert_optional_number_field(stats, 'lastPacketSentTimestamp'); assert_optional_number_field(stats, 'targetBitrate'); - assert_optional_unsigned_int_field(stats, 'framesEncoded'); + assert_unsigned_int_field(stats, 'framesEncoded'); assert_optional_number_field(stats, 'totalEncodeTime'); assert_optional_number_field(stats, 'averageRTCPInterval'); } @@ -383,17 +374,20 @@ function validateOutboundRtpStreamStats(statsReport, stats) { [webrtc-stats] 7.8. RTCRemoteOutboundRTPStreamStats dictionary dictionary RTCRemoteOutboundRTPStreamStats : RTCSentRTPStreamStats { - [RTCInboundRTPStreamStats] DOMString localId; - DOMHighResTimeStamp remoteTimestamp; }; + + [webrtc-pc] + 8.6. Mandatory To Implement Stats + - RTCRemoteOutboundRTPStreamStats, with all required attributes from its + inherited dictionaries, and also attributes localId, remoteTimestamp */ function validateRemoteOutboundRtpStreamStats(statsReport, stats) { validateSentRtpStreamStats(statsReport, stats); - validateOptionalIdField(statsReport, stats, 'localId', 'inbound-rtp'); - assert_optional_number_field(stats, 'remoteTimeStamp'); + validateIdField(statsReport, stats, 'localId', 'inbound-rtp'); + assert_number_field(stats, 'remoteTimeStamp'); } /* @@ -401,10 +395,7 @@ function validateRemoteOutboundRtpStreamStats(statsReport, stats) { 7.9. RTCRTPContributingSourceStats dictionary RTCRTPContributingSourceStats : RTCStats { unsigned long contributorSsrc; - - [RTCInboundRTPStreamStats] DOMString inboundRtpStreamId; - unsigned long packetsContributedTo; double audioLevel; }; @@ -423,10 +414,10 @@ function validateContributingSourceStats(statsReport, stats) { [webrtc-stats] 7.10. RTCPeerConnectionStats dictionary dictionary RTCPeerConnectionStats : RTCStats { - required unsigned long dataChannelsOpened; - required unsigned long dataChannelsClosed; - unsigned long dataChannelsRequested; - unsigned long dataChannelsAccepted; + unsigned long dataChannelsOpened; + unsigned long dataChannelsClosed; + unsigned long dataChannelsRequested; + unsigned long dataChannelsAccepted; }; [webrtc-pc] @@ -446,10 +437,8 @@ function validatePeerConnectionStats(statsReport, stats) { [webrtc-stats] 7.11. RTCMediaStreamStats dictionary dictionary RTCMediaStreamStats : RTCStats { - required DOMString streamIdentifier; - - [RTCMediaStreamTrackStats] - required sequence<DOMString> trackIds; + DOMString streamIdentifier; + sequence<DOMString> trackIds; }; [webrtc-pc] @@ -479,35 +468,37 @@ function validateMediaStreamStats(statsReport, stats) { [webrtc-stats] 7.12. RTCMediaStreamTrackStats dictionary dictionary RTCMediaStreamTrackStats : RTCStats { - required DOMString trackIdentifier; - required boolean remoteSource; - required boolean ended; - required boolean detached; - DOMString kind; - DOMHighResTimeStamp estimatedPlayoutTimestamp; - required unsigned long frameWidth; - required unsigned long frameHeight; - required double framesPerSecond; - unsigned long framesCaptured; - required unsigned long framesSent; - required unsigned long framesReceived; - required unsigned long framesDecoded; - required unsigned long framesDropped; - required unsigned long framesCorrupted; - unsigned long partialFramesLost; - unsigned long fullFramesLost; - required double audioLevel; - double totalAudioEnergy; - boolean voiceActivityFlag; - double echoReturnLoss; - double echoReturnLossEnhancement; - unsigned long long totalSamplesSent; - unsigned long long totalSamplesReceived; - double totalSamplesDuration; - unsigned long long concealedSamples; - unsigned long long concealmentEvents; - double jitterBufferDelay; - RTCPriorityType priority; + DOMString trackIdentifier; + boolean remoteSource; + boolean ended; + boolean detached; + DOMString kind; + DOMHighResTimeStamp estimatedPlayoutTimestamp; + unsigned long frameWidth; + unsigned long frameHeight; + double framesPerSecond; + unsigned long framesCaptured; + unsigned long framesSent; + unsigned long keyFramesSent; + unsigned long framesReceived; + unsigned long keyFramesReceived; + unsigned long framesDecoded; + unsigned long framesDropped; + unsigned long framesCorrupted; + unsigned long partialFramesLost; + unsigned long fullFramesLost; + double audioLevel; + double totalAudioEnergy; + boolean voiceActivityFlag; + double echoReturnLoss; + double echoReturnLossEnhancement; + unsigned long long totalSamplesSent; + unsigned long long totalSamplesReceived; + double totalSamplesDuration; + unsigned long long concealedSamples; + unsigned long long concealmentEvents; + double jitterBufferDelay; + RTCPriorityType priority; }; [webrtc-pc] @@ -520,48 +511,50 @@ function validateMediaStreamStats(statsReport, stats) { }; 8.6. Mandatory To Implement Stats - - RTCMediaStreamTrackStats, with attributes trackIdentifier, remoteSource, ended, - detached, ssrcIds, frameWidth, frameHeight, framesPerSecond, framesSent, + - RTCMediaStreamTrackStats, with attributes trackIdentifier, remoteSource, + ended, detached, frameWidth, frameHeight, framesPerSecond, framesSent, framesReceived, framesDecoded, framesDropped, framesCorrupted, audioLevel */ -function validateMediaStreamTrackStats(stats, stat) { +function validateMediaStreamTrackStats(statsReport, stats) { validateRtcStats(statsReport, stats); - assert_string_field(stat, 'trackIdentifier'); - assert_boolean_field(stat, 'remoteSource'); - assert_boolean_field(stat, 'ended'); - assert_boolean_field(stat, 'detached'); - - assert_optional_string_field(stat, 'kind'); - assert_optional_number_field(stat, 'estimatedPlayoutTimestamp'); - - assert_unsigned_int_field(stat, 'frameWidth'); - assert_unsigned_int_field(stat, 'frameHeight'); - assert_number_field(stat, 'framesPerSecond'); - - assert_optional_unsigned_int_field(stat, 'framesCaptured'); - assert_unsigned_int_field(stat, 'frameSent'); - assert_unsigned_int_field(stat, 'frameReceived'); - assert_unsigned_int_field(stat, 'frameDecoded'); - assert_unsigned_int_field(stat, 'frameDropped'); - assert_unsigned_int_field(stat, 'frameCorrupted'); - - assert_optional_unsigned_int_field(stat, 'partialFramesLost'); - assert_optional_unsigned_int_field(stat, 'fullFramesLost'); - - assert_number_field(stat, 'audioLevel'); - assert_optional_number_field(stat, 'totalAudioEnergy'); - assert_optional_boolean_field(stat, 'voiceActivityFlag'); - assert_optional_number_field(stat, 'echoReturnLoss'); - assert_optional_number_field(stat, 'echoReturnLossEnhancement'); - - assert_optional_unsigned_int_field(stat, 'totalSamplesSent'); - assert_optional_unsigned_int_field(stat, 'totalSamplesReceived'); - assert_optional_number_field(stat, 'totalSamplesDuration'); - assert_optional_unsigned_int_field(stat, 'concealedSamples'); - assert_optional_unsigned_int_field(stat, 'concealmentEvents'); - assert_optional_number_field(stat, 'jitterBufferDelay'); + assert_string_field(stats, 'trackIdentifier'); + assert_boolean_field(stats, 'remoteSource'); + assert_boolean_field(stats, 'ended'); + assert_boolean_field(stats, 'detached'); + + assert_optional_enum_field(stats, 'kind', ['audio', 'video']); + assert_optional_number_field(stats, 'estimatedPlayoutTimestamp'); + + assert_unsigned_int_field(stats, 'frameWidth'); + assert_unsigned_int_field(stats, 'frameHeight'); + assert_number_field(stats, 'framesPerSecond'); + + assert_optional_unsigned_int_field(stats, 'framesCaptured'); + assert_unsigned_int_field(stats, 'framesSent'); + assert_optional_unsigned_int_field(stats, 'keyFramesSent'); + assert_unsigned_int_field(stats, 'framesReceived'); + assert_optional_unsigned_int_field(stats, 'keyFramesReceived'); + assert_unsigned_int_field(stats, 'framesDecoded'); + assert_unsigned_int_field(stats, 'framesDropped'); + assert_unsigned_int_field(stats, 'framesCorrupted'); + + assert_optional_unsigned_int_field(stats, 'partialFramesLost'); + assert_optional_unsigned_int_field(stats, 'fullFramesLost'); + + assert_number_field(stats, 'audioLevel'); + assert_optional_number_field(stats, 'totalAudioEnergy'); + assert_optional_boolean_field(stats, 'voiceActivityFlag'); + assert_optional_number_field(stats, 'echoReturnLoss'); + assert_optional_number_field(stats, 'echoReturnLossEnhancement'); + + assert_optional_unsigned_int_field(stats, 'totalSamplesSent'); + assert_optional_unsigned_int_field(stats, 'totalSamplesReceived'); + assert_optional_number_field(stats, 'totalSamplesDuration'); + assert_optional_unsigned_int_field(stats, 'concealedSamples'); + assert_optional_unsigned_int_field(stats, 'concealmentEvents'); + assert_optional_number_field(stats, 'jitterBufferDelay'); assert_optional_enum_field(stats, 'priority', ['very-low', 'low', 'medium', 'high']); @@ -571,18 +564,15 @@ function validateMediaStreamTrackStats(stats, stat) { [webrtc-stats] 7.13. RTCDataChannelStats dictionary dictionary RTCDataChannelStats : RTCStats { - required DOMString label; - required DOMString protocol; - required long datachannelid; - - [RTCTransportStats] - DOMString transportId; - - required RTCDataChannelState state; - required unsigned long messagesSent; - required unsigned long long bytesSent; - required unsigned long messagesReceived; - required unsigned long long bytesReceived; + DOMString label; + DOMString protocol; + long dataChannelIdentifier; + DOMString transportId; + RTCDataChannelState state; + unsigned long messagesSent; + unsigned long long bytesSent; + unsigned long messagesReceived; + unsigned long long bytesReceived; }; [webrtc-pc] @@ -598,22 +588,19 @@ function validateMediaStreamTrackStats(stats, stat) { - RTCDataChannelStats, with attributes label, protocol, datachannelId, state, messagesSent, bytesSent, messagesReceived, bytesReceived */ - function validateDataChannelStats(statsReport, stats) { validateRtcStats(statsReport, stats); assert_string_field(stats, 'label'); assert_string_field(stats, 'protocol'); - assert_int_field(stats, 'datachannelid'); + assert_int_field(stats, 'dataChannelIdentifier'); validateOptionalIdField(statsReport, stats, 'transportId', 'transport'); assert_enum_field(stats, 'state', ['connecting', 'open', 'closing', 'closed']); - assert_unsigned_int_field(stats, 'messageSent'); - - assert_unsigned_int_field(stats, 'messageSent'); + assert_unsigned_int_field(stats, 'messagesSent'); assert_unsigned_int_field(stats, 'bytesSent'); assert_unsigned_int_field(stats, 'messagesReceived'); assert_unsigned_int_field(stats, 'bytesReceived'); @@ -623,25 +610,16 @@ function validateDataChannelStats(statsReport, stats) { [webrtc-stats] 7.14. RTCTransportStats dictionary dictionary RTCTransportStats : RTCStats { - unsigned long packetsSent; - unsigned long packetsReceived; - required unsigned long long bytesSent; - required unsigned long long bytesReceived; - - [RTCTransportStats] - required DOMString rtcpTransportStatsId; - - RTCIceRole iceRole; - RTCDtlsTransportState dtlsState; - - [RTCIceCandidatePairStats] - required DOMString selectedCandidatePairId; - - [RTCCertificateStats] - required DOMString localCertificateId; - - [RTCCertificateStats] - required DOMString remoteCertificateId; + unsigned long packetsSent; + unsigned long packetsReceived; + unsigned long long bytesSent; + unsigned long long bytesReceived; + DOMString rtcpTransportStatsId; + RTCIceRole iceRole; + RTCDtlsTransportState dtlsState; + DOMString selectedCandidatePairId; + DOMString localCertificateId; + DOMString remoteCertificateId; }; [webrtc-pc] @@ -661,10 +639,10 @@ function validateDataChannelStats(statsReport, stats) { }; 8.6. Mandatory To Implement Stats - - RTCTransportStats, with attributes bytesSent, bytesReceived, rtcpTransportStatsId, - activeConnection, selectedCandidatePairId, localCertificateId, remoteCertificateId + - RTCTransportStats, with attributes bytesSent, bytesReceived, + rtcpTransportStatsId, selectedCandidatePairId, localCertificateId, + remoteCertificateId */ - function validateTransportStats(statsReport, stats) { validateRtcStats(statsReport, stats); @@ -682,26 +660,35 @@ function validateTransportStats(statsReport, stats) { ['new', 'connecting', 'connected', 'closed', 'failed']); validateIdField(statsReport, stats, 'selectedCandidatePairId', 'candidate-pair'); - validateIdField(stateReport, stats, 'localCertificateId', 'certificate'); - validateIdField(stateReport, stats, 'remoteCertificateId', 'certificate'); + validateIdField(statsReport, stats, 'localCertificateId', 'certificate'); + validateIdField(statsReport, stats, 'remoteCertificateId', 'certificate'); } /* [webrtc-stats] 7.15. RTCIceCandidateStats dictionary dictionary RTCIceCandidateStats : RTCStats { - [RTCTransportStats] - DOMString transportId; - - boolean isRemote; - required DOMString ip; - required long port; - required DOMString protocol; - required RTCIceCandidateType candidateType; - required long priority; - required DOMString url; - DOMString relayProtocol; - boolean deleted = false; + DOMString transportId; + boolean isRemote; + RTCNetworkType networkType; + DOMString ip; + long port; + DOMString protocol; + RTCIceCandidateType candidateType; + long priority; + DOMString url; + DOMString relayProtocol; + boolean deleted = false; + }; + + enum RTCNetworkType { + "bluetooth", + "cellular", + "ethernet", + "wifi", + "wimax", + "vpn", + "unknown" }; [webrtc-pc] @@ -714,16 +701,18 @@ function validateTransportStats(statsReport, stats) { }; 8.6. Mandatory To Implement Stats - - RTCIceCandidateStats, with attributes ip, port, protocol, candidateType, priority, - url + - RTCIceCandidateStats, with attributes ip, port, protocol, candidateType, + priority, url */ - function validateIceCandidateStats(statsReport, stats) { validateRtcStats(statsReport, stats); validateOptionalIdField(statsReport, stats, 'transportId', 'transport'); assert_optional_boolean_field(stats, 'isRemote'); + assert_optional_enum_field(stats, 'networkType', + ['bluetooth', 'cellular', 'ethernet', 'wifi', 'wimax', 'vpn', 'unknown']) + assert_string_field(stats, 'ip'); assert_int_field(stats, 'port'); assert_string_field(stats, 'protocol'); @@ -741,40 +730,34 @@ function validateIceCandidateStats(statsReport, stats) { [webrtc-stats] 7.16. RTCIceCandidatePairStats dictionary dictionary RTCIceCandidatePairStats : RTCStats { - [RTCTransportStats] - required DOMString transportId; - - [RTCIceCandidateStats] - required DOMString localCandidateId; - - [RTCIceCandidateStats] - required DOMString remoteCandidateId; - - required RTCStatsIceCandidatePairState state; - required unsigned long long priority; - required boolean nominated; - unsigned long packetsSent; - unsigned long packetsReceived; - required unsigned long long bytesSent; - required unsigned long long bytesReceived; - DOMHighResTimeStamp lastPacketSentTimestamp; - DOMHighResTimeStamp lastPacketReceivedTimestamp; - DOMHighResTimeStamp firstRequestTimestamp; - DOMHighResTimeStamp lastRequestTimestamp; - DOMHighResTimeStamp lastResponseTimestamp; - required double totalRoundTripTime; - required double currentRoundTripTime; - double availableOutgoingBitrate; - double availableIncomingBitrate; - unsigned long circuitBreakerTriggerCount; - unsigned long long requestsReceived; - unsigned long long requestsSent; - unsigned long long responsesReceived; - unsigned long long responsesSent; - unsigned long long retransmissionsReceived; - unsigned long long retransmissionsSent; - unsigned long long consentRequestsSent; - DOMHighResTimeStamp consentExpiredTimestamp; + DOMString transportId; + DOMString localCandidateId; + DOMString remoteCandidateId; + RTCStatsIceCandidatePairState state; + unsigned long long priority; + boolean nominated; + unsigned long packetsSent; + unsigned long packetsReceived; + unsigned long long bytesSent; + unsigned long long bytesReceived; + DOMHighResTimeStamp lastPacketSentTimestamp; + DOMHighResTimeStamp lastPacketReceivedTimestamp; + DOMHighResTimeStamp firstRequestTimestamp; + DOMHighResTimeStamp lastRequestTimestamp; + DOMHighResTimeStamp lastResponseTimestamp; + double totalRoundTripTime; + double currentRoundTripTime; + double availableOutgoingBitrate; + double availableIncomingBitrate; + unsigned long circuitBreakerTriggerCount; + unsigned long long requestsReceived; + unsigned long long requestsSent; + unsigned long long responsesReceived; + unsigned long long responsesSent; + unsigned long long retransmissionsReceived; + unsigned long long retransmissionsSent; + unsigned long long consentRequestsSent; + DOMHighResTimeStamp consentExpiredTimestamp; }; enum RTCStatsIceCandidatePairState { @@ -788,8 +771,7 @@ function validateIceCandidateStats(statsReport, stats) { [webrtc-pc] 8.6. Mandatory To Implement Stats - RTCIceCandidatePairStats, with attributes transportId, localCandidateId, - remoteCandidateId, state, priority, nominated, writable, readable, bytesSent, - bytesReceived, totalRtt, currentRtt + remoteCandidateId, state, priority, nominated, bytesSent, bytesReceived, totalRoundTripTime, currentRoundTripTime */ function validateIceCandidatePairStats(statsReport, stats) { validateRtcStats(statsReport, stats); @@ -835,10 +817,10 @@ function validateIceCandidatePairStats(statsReport, stats) { [webrtc-stats] 7.17. RTCCertificateStats dictionary dictionary RTCCertificateStats : RTCStats { - required DOMString fingerprint; - required DOMString fingerprintAlgorithm; - required DOMString base64Certificate; - required DOMString issuerCertificateId; + DOMString fingerprint; + DOMString fingerprintAlgorithm; + DOMString base64Certificate; + DOMString issuerCertificateId; }; [webrtc-pc] @@ -846,7 +828,6 @@ function validateIceCandidatePairStats(statsReport, stats) { - RTCCertificateStats, with attributes fingerprint, fingerprintAlgorithm, base64Certificate, issuerCertificateId */ - function validateCertificateStats(statsReport, stats) { validateRtcStats(statsReport, stats); diff --git a/tests/wpt/web-platform-tests/webrtc/RTCTrackEvent-constructor.html b/tests/wpt/web-platform-tests/webrtc/RTCTrackEvent-constructor.html index d5c5f621f80..9579dd4d4f8 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCTrackEvent-constructor.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCTrackEvent-constructor.html @@ -41,6 +41,7 @@ assert_equals(trackEvent.receiver, receiver); assert_equals(trackEvent.track, track); assert_array_equals(trackEvent.streams, []); + assert_equals(trackEvent.streams, trackEvent.streams); // [SameObject] assert_equals(trackEvent.transceiver, transceiver); assert_equals(trackEvent.type, 'track'); diff --git a/tests/wpt/web-platform-tests/webrtc/datachannel-idlharness.html b/tests/wpt/web-platform-tests/webrtc/datachannel-idlharness.html deleted file mode 100644 index ea2dd2a3df9..00000000000 --- a/tests/wpt/web-platform-tests/webrtc/datachannel-idlharness.html +++ /dev/null @@ -1,75 +0,0 @@ -<!doctype html> -<!-- -This test creates a data channel object and compares it with the WebIDL defined interface ---> - -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> - <title>RTCPeerConnection Data Channel Empty String Test</title> - <link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/> - <link rel="help" href="http://w3c.github.io/webrtc-pc/#rtcdatachannel"> -</head> -<body> - <div id="log"></div> - <!-- These files are in place when executing on W3C. --> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> - <script src=/resources/WebIDLParser.js></script> - <script src=/resources/idlharness.js></script> - <!-- The IDL is copied from https://w3c.github.io/webrtc-pc/archives/20161219/webrtc.html --> - <script type="text/plain"> -interface RTCDataChannel : EventTarget { - readonly attribute USVString label; - readonly attribute boolean ordered; - readonly attribute unsigned short? maxPacketLifeTime; - readonly attribute unsigned short? maxRetransmits; - readonly attribute USVString protocol; - readonly attribute boolean negotiated; - readonly attribute unsigned short id; - readonly attribute RTCPriorityType priority; - readonly attribute RTCDataChannelState readyState; - readonly attribute unsigned long bufferedAmount; - attribute unsigned long bufferedAmountLowThreshold; - attribute EventHandler onopen; - attribute EventHandler onbufferedamountlow; - attribute EventHandler onerror; - attribute EventHandler onclose; - void close(); - attribute EventHandler onmessage; - attribute DOMString binaryType; - void send(USVString data); - void send(Blob data); - void send(ArrayBuffer data); - void send(ArrayBufferView data); -}; -enum RTCPriorityType { - "very-low", - "low", - "medium", - "high" -}; -enum RTCDataChannelState { - "connecting", - "open", - "closing", - "closed" -}; -</script> - <script type="text/javascript"> - (function() { - var idl_array = new IdlArray(); - [].forEach.call(document.querySelectorAll("script[type=text\\/plain]"), - function(node) { - idl_array.add_idls(node.textContent); - }); - pc = new RTCPeerConnection(null); - window.channel = pc.createDataChannel("test"); - idl_array.add_objects({"RTCDataChannel": ["channel"]}); - idl_array.test(); - done(); -})(); -</script> -</body> -</html> - diff --git a/tests/wpt/web-platform-tests/webrtc/tools/.eslintrc.js b/tests/wpt/web-platform-tests/webrtc/tools/.eslintrc.js new file mode 100644 index 00000000000..47bbb5dc024 --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/tools/.eslintrc.js @@ -0,0 +1,156 @@ +module.exports = { + rules: { + 'no-undef': 1, + 'no-unused-vars': 0 + }, + plugins: [ + 'html' + ], + env: { + browser: true, + es6: true + }, + globals: { + // testharness globals + test: true, + async_test: true, + promise_test: true, + promise_rejects: true, + IdlArray: true, + assert_true: true, + assert_false: true, + assert_equals: true, + assert_not_equals: true, + assert_array_equals: true, + assert_in_array: true, + assert_unreached: true, + assert_throws: true, + assert_idl_attribute: true, + assert_exists: true, + assert_greater_than: true, + assert_less_than: true, + assert_greater_than_equal: true, + assert_less_than_equal: true, + assert_approx_equals: true, + + + // WebRTC globals + RTCPeerConnection: true, + RTCRtpSender: true, + RTCRtpReceiver: true, + RTCRtpTransceiver: true, + RTCIceTransport: true, + RTCDtlsTransport: true, + RTCSctpTransport: true, + RTCDataChannel: true, + RTCCertificate: true, + RTCDTMFSender: true, + RTCError: true, + RTCTrackEvent: true, + RTCPeerConnectionIceEvent: true, + RTCDTMFToneChangeEvent: true, + RTCDataChannelEvent: true, + RTCRtpContributingSource: true, + RTCRtpSynchronizationSource: true, + + // dictionary-helper.js + assert_unsigned_int_field: true, + assert_int_field: true, + assert_string_field: true, + assert_number_field: true, + assert_boolean_field: true, + assert_array_field: true, + assert_dict_field: true, + assert_enum_field: true, + + assert_optional_unsigned_int_field: true, + assert_optional_int_field: true, + assert_optional_string_field: true, + assert_optional_number_field: true, + assert_optional_boolean_field: true, + assert_optional_array_field: true, + assert_optional_dict_field: true, + assert_optional_enum_field: true, + + // identity-helper.js + parseAssertionResult: true, + getIdpDomains: true, + assert_rtcerror_rejection: true, + hostString: true, + + // RTCConfiguration-helper.js + config_test: true, + + // RTCDTMFSender-helper.js + createDtmfSender: true, + test_tone_change_events: true, + getTransceiver: true, + + // RTCPeerConnection-helper.js + countLine: true, + countAudioLine: true, + countVideoLine: true, + countApplicationLine: true, + similarMediaDescriptions: true, + assert_is_session_description: true, + isSimilarSessionDescription: true, + assert_session_desc_equals: true, + assert_session_desc_not_equals: true, + generateOffer: true, + generateAnswer: true, + test_state_change_event: true, + test_never_resolve: true, + exchangeIceCandidates: true, + doSignalingHandshake: true, + createDataChannelPair: true, + awaitMessage: true, + blobToArrayBuffer: true, + assert_equals_array_buffer: true, + generateMediaStreamTrack: true, + getTrackFromUserMedia: true, + getUserMediaTracksAndStreams: true, + performOffer: true, + Resolver: true, + + // RTCRtpCapabilities-helper.js + validateRtpCapabilities: true, + validateCodecCapability: true, + validateHeaderExtensionCapability: true, + + // RTCRtpParameters-helper.js + validateSenderRtpParameters: true, + validateReceiverRtpParameters: true, + validateRtpParameters: true, + validateEncodingParameters: true, + validateRtcpParameters: true, + validateHeaderExtensionParameters: true, + validateCodecParameters: true, + + // RTCStats-helper.js + validateStatsReport: true, + assert_stats_report_has_stats: true, + findStatsFromReport: true, + getRequiredStats: true, + getStatsById: true, + validateIdField: true, + validateOptionalIdField: true, + validateRtcStats: true, + validateRtpStreamStats: true, + validateCodecStats: true, + validateReceivedRtpStreamStats: true, + validateInboundRtpStreamStats: true, + validateRemoteInboundRtpStreamStats: true, + validateSentRtpStreamStats: true, + validateOutboundRtpStreamStats: true, + validateRemoteOutboundRtpStreamStats: true, + validateContributingSourceStats: true, + validatePeerConnectionStats: true, + validateMediaStreamStats: true, + validateMediaStreamTrackStats: true, + validateDataChannelStats: true, + validateTransportStats: true, + validateIceCandidateStats: true, + validateIceCandidatePairStats: true, + validateCertificateStats: true, + } +} diff --git a/tests/wpt/web-platform-tests/webrtc/tools/README.md b/tests/wpt/web-platform-tests/webrtc/tools/README.md new file mode 100644 index 00000000000..68bc284fdfa --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/tools/README.md @@ -0,0 +1,14 @@ +WebRTC Tools +============ + +This directory contains a simple Node.js project to aid the development of +WebRTC tests. + +## Lint + +```bash +npm run lint +``` + +Does basic linting of the JavaScript code. Mainly for catching usage of +undefined variables. diff --git a/tests/wpt/web-platform-tests/webrtc/tools/package-lock.json b/tests/wpt/web-platform-tests/webrtc/tools/package-lock.json new file mode 100644 index 00000000000..79e603949cf --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/tools/package-lock.json @@ -0,0 +1,1213 @@ +{ + "name": "webrtc-testing-tools", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", + "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz", + "integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "chardet": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.0.tgz", + "integrity": "sha1-C74TVaxE16PtSpJXB8TvcPgZD2w=", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "dev": true, + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", + "integrity": "sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=", + "dev": true, + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.11.0.tgz", + "integrity": "sha512-UWbhQpaKlm8h5x/VLwm0S1kheMrDj8jPwhnBMjr/Dlo3qqT7MvcN/UfKAR3E1N4lr4YNtOvS4m3hwsrVc/ky7g==", + "dev": true, + "requires": { + "ajv": "5.3.0", + "babel-code-frame": "6.26.0", + "chalk": "2.3.0", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.0.0", + "eslint-scope": "3.7.1", + "espree": "3.5.2", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.0.0", + "js-yaml": "3.10.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + } + }, + "eslint-plugin-html": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-4.0.0.tgz", + "integrity": "sha512-xK/909qOTq5JVzuO2jo4a24nQcWhkOBz9dOIkORvB7RxC75a4b6B9wFpBXAl8WDhwJGFDj5gBDRN+/L3kK/ghw==", + "dev": true, + "requires": { + "htmlparser2": "3.9.2" + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "espree": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", + "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "dev": true, + "requires": { + "acorn": "5.2.1", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "dev": true, + "requires": { + "chardet": "0.4.0", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" + } + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.4.1", + "domutils": "1.6.2", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.3.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.1.0", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.3.0", + "ajv-keywords": "2.1.1", + "chalk": "2.3.0", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } +} diff --git a/tests/wpt/web-platform-tests/webrtc/tools/package.json b/tests/wpt/web-platform-tests/webrtc/tools/package.json new file mode 100644 index 00000000000..70515d8b715 --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/tools/package.json @@ -0,0 +1,14 @@ +{ + "name": "webrtc-testing-tools", + "version": "1.0.0", + "description": "Tools for WebRTC testing", + "scripts": { + "lint": "eslint -c .eslintrc.js ../*.html ../*.js" + }, + "devDependencies": { + "eslint": "^4.11.0", + "eslint-plugin-html": "^4.0.0" + }, + "license": "BSD", + "private": true +} |