mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 04:27:37 +00:00
Bug 1119593 - Update WebRTC tests to use promises more consistently, r=drno,jib
--HG-- extra : rebase_source : 372f545072260a3b27963eae126ace7e3756f2c3
This commit is contained in:
parent
8966c2b84a
commit
52eec4cce1
@ -2,6 +2,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
var Cr = SpecialPowers.Cr;
|
||||
@ -101,18 +103,14 @@ function createMediaElement(type, label) {
|
||||
*
|
||||
* @param {Dictionary} constraints
|
||||
* The constraints for this mozGetUserMedia callback
|
||||
* @param {Function} onSuccess
|
||||
* The success callback if the stream is successfully retrieved
|
||||
* @param {Function} onError
|
||||
* The error callback if the stream fails to be retrieved
|
||||
*/
|
||||
function getUserMedia(constraints, onSuccess, onError) {
|
||||
function getUserMedia(constraints) {
|
||||
if (!("fake" in constraints) && FAKE_ENABLED) {
|
||||
constraints["fake"] = FAKE_ENABLED;
|
||||
}
|
||||
|
||||
info("Call getUserMedia for " + JSON.stringify(constraints));
|
||||
navigator.mozGetUserMedia(constraints, onSuccess, onError);
|
||||
return navigator.mediaDevices.getUserMedia(constraints);
|
||||
}
|
||||
|
||||
|
||||
@ -198,29 +196,47 @@ function checkMediaStreamTracks(constraints, mediaStream) {
|
||||
mediaStream.getVideoTracks());
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility methods
|
||||
*/
|
||||
/*** Utility methods */
|
||||
|
||||
/** The dreadful setTimeout, use sparingly */
|
||||
function wait(time) {
|
||||
return new Promise(r => setTimeout(r, time));
|
||||
}
|
||||
|
||||
/** The even more dreadful setInterval, use even more sparingly */
|
||||
function waitUntil(func, time) {
|
||||
return new Promise(resolve => {
|
||||
var interval = setInterval(() => {
|
||||
if (func()) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, time || 200);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of a blob as text
|
||||
*
|
||||
* @param {Blob} blob
|
||||
The blob to retrieve the contents from
|
||||
* @param {Function} onSuccess
|
||||
Callback with the blobs content as parameter
|
||||
*/
|
||||
function getBlobContent(blob, onSuccess) {
|
||||
var reader = new FileReader();
|
||||
function getBlobContent(blob) {
|
||||
return new Promise(resolve => {
|
||||
var reader = new FileReader();
|
||||
|
||||
// Listen for 'onloadend' which will always be called after a success or failure
|
||||
reader.onloadend = function (event) {
|
||||
onSuccess(event.target.result);
|
||||
};
|
||||
// Listen for 'onloadend' which will always be called after a success or failure
|
||||
reader.onloadend = function (event) {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
|
||||
reader.readAsText(blob);
|
||||
reader.readAsText(blob);
|
||||
});
|
||||
}
|
||||
|
||||
/*** Test control flow methods */
|
||||
|
||||
/**
|
||||
* Generates a callback function fired only under unexpected circumstances
|
||||
* while running the tests. The generated function kills off the test as well
|
||||
@ -238,7 +254,7 @@ function generateErrorCallback(message) {
|
||||
* @param {object} aObj
|
||||
* The object fired back from the callback
|
||||
*/
|
||||
return function (aObj) {
|
||||
return aObj => {
|
||||
if (aObj) {
|
||||
if (aObj.name && aObj.message) {
|
||||
ok(false, "Unexpected callback for '" + aObj.name +
|
||||
@ -252,10 +268,15 @@ function generateErrorCallback(message) {
|
||||
ok(false, "Unexpected callback with message = '" + message +
|
||||
"' at: " + JSON.stringify(stack));
|
||||
}
|
||||
SimpleTest.finish();
|
||||
throw new Error("Unexpected callback");
|
||||
}
|
||||
}
|
||||
|
||||
var unexpectedEventArrived;
|
||||
var unexpectedEventArrivedPromise = new Promise((x, reject) => {
|
||||
unexpectedEventArrived = reject;
|
||||
});
|
||||
|
||||
/**
|
||||
* Generates a callback function fired only for unexpected events happening.
|
||||
*
|
||||
@ -264,17 +285,248 @@ function generateErrorCallback(message) {
|
||||
* @param {String} eventName
|
||||
Name of the unexpected event
|
||||
*/
|
||||
function unexpectedEventAndFinish(message, eventName) {
|
||||
function unexpectedEvent(message, eventName) {
|
||||
var stack = new Error().stack.split("\n");
|
||||
stack.shift(); // Don't include this instantiation frame
|
||||
|
||||
return function () {
|
||||
ok(false, "Unexpected event '" + eventName + "' fired with message = '" +
|
||||
message + "' at: " + JSON.stringify(stack));
|
||||
SimpleTest.finish();
|
||||
return e => {
|
||||
var details = "Unexpected event '" + eventName + "' fired with message = '" +
|
||||
message + "' at: " + JSON.stringify(stack);
|
||||
ok(false, details);
|
||||
unexpectedEventArrived(new Error(details));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the event guard pattern used throughout. Each of the 'onxxx'
|
||||
* attributes on the wrappers can be set with a custom handler. Prior to the
|
||||
* handler being set, if the event fires, it causes the test execution to halt.
|
||||
* Once but that handler is used exactly once, and subsequent events will also
|
||||
* cause test execution to halt.
|
||||
*
|
||||
* @param {object} wrapper
|
||||
* The wrapper on which the psuedo-handler is installed
|
||||
* @param {object} obj
|
||||
* The real source of events
|
||||
* @param {string} event
|
||||
* The name of the event
|
||||
* @param {function} redirect
|
||||
* (Optional) a function that determines what is passed to the event
|
||||
* handler. By default, the handler is passed the wrapper (as opposed to
|
||||
* the normal cases where they receive an event object). This redirect
|
||||
* function is passed the event.
|
||||
*/
|
||||
function guardEvent(wrapper, obj, event, redirect) {
|
||||
redirect = redirect || (e => wrapper);
|
||||
var onx = 'on' + event;
|
||||
var unexpected = unexpectedEvent(wrapper, event);
|
||||
wrapper[onx] = unexpected;
|
||||
obj[onx] = e => {
|
||||
info(wrapper + ': "on' + event + '" event fired');
|
||||
wrapper[onx](redirect(e));
|
||||
wrapper[onx] = unexpected;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This class executes a series of functions in a continuous sequence.
|
||||
* Promise-bearing functions are executed after the previous promise completes.
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} framework
|
||||
* A back reference to the framework which makes use of the class. It is
|
||||
* passed to each command callback.
|
||||
* @param {function[]} commandList
|
||||
* Commands to set during initialization
|
||||
*/
|
||||
function CommandChain(framework, commandList) {
|
||||
this._framework = framework;
|
||||
this.commands = commandList || [ ];
|
||||
}
|
||||
|
||||
CommandChain.prototype = {
|
||||
/**
|
||||
* Start the command chain. This returns a promise that always resolves
|
||||
* cleanly (this catches errors and fails the test case).
|
||||
*/
|
||||
execute: function () {
|
||||
return this.commands.reduce((prev, next, i) => {
|
||||
if (typeof next !== 'function' || !next.name) {
|
||||
throw new Error('registered non-function' + next);
|
||||
}
|
||||
|
||||
return prev.then(() => {
|
||||
info('Run step ' + (i + 1) + ': ' + next.name);
|
||||
return Promise.race([
|
||||
next(this._framework),
|
||||
unexpectedEventArrivedPromise
|
||||
]);
|
||||
});
|
||||
}, Promise.resolve())
|
||||
.catch(e =>
|
||||
ok(false, 'Error in test execution: ' + e +
|
||||
((typeof e.stack === 'string') ?
|
||||
(' ' + e.stack.split('\n').join(' ... ')) : '')));
|
||||
},
|
||||
|
||||
/**
|
||||
* Add new commands to the end of the chain
|
||||
*
|
||||
* @param {function[]} commands
|
||||
* List of command functions
|
||||
*/
|
||||
append: function(commands) {
|
||||
this.commands = this.commands.concat(commands);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the index of the specified command in the chain.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @returns {number} Index of the command
|
||||
*/
|
||||
indexOf: function (id) {
|
||||
if (typeof id === 'string') {
|
||||
return this.commands.findIndex(f => f.name === id);
|
||||
}
|
||||
return this.commands.indexOf(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts the new commands after the specified command.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @param {function[]} commands
|
||||
* List of commands
|
||||
*/
|
||||
insertAfter: function (id, commands) {
|
||||
this._insertHelper(id, commands, 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts the new commands before the specified command.
|
||||
*
|
||||
* @param {string} id
|
||||
* Command function or name
|
||||
* @param {function[]} commands
|
||||
* List of commands
|
||||
*/
|
||||
insertBefore: function (id, commands) {
|
||||
this._insertHelper(id, commands);
|
||||
},
|
||||
|
||||
_insertHelper: function(id, commands, delta) {
|
||||
delta = delta || 0;
|
||||
var index = this.indexOf(id);
|
||||
|
||||
if (index >= 0) {
|
||||
this.commands = [].concat(
|
||||
this.commands.slice(0, index + delta),
|
||||
commands,
|
||||
this.commands.slice(index + delta));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the specified command
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @returns {function[]} Removed commands
|
||||
*/
|
||||
remove: function (id) {
|
||||
var index = this.indexOf(id);
|
||||
if (index >= 0) {
|
||||
return this.commands.splice(index, 1);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all commands after the specified one.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @returns {function[]} Removed commands
|
||||
*/
|
||||
removeAfter: function (id) {
|
||||
var index = this.indexOf(id);
|
||||
if (index >= 0) {
|
||||
return this.commands.splice(index + 1);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all commands before the specified one.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @returns {function[]} Removed commands
|
||||
*/
|
||||
removeBefore: function (id) {
|
||||
var index = this.indexOf(id);
|
||||
if (index >= 0) {
|
||||
return this.commands.splice(0, index);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces a single command.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @param {function[]} commands
|
||||
* List of commands
|
||||
* @returns {function[]} Removed commands
|
||||
*/
|
||||
replace: function (id, commands) {
|
||||
this.insertBefore(id, commands);
|
||||
return this.remove(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces all commands after the specified one.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @returns {object[]} Removed commands
|
||||
*/
|
||||
replaceAfter: function (id, commands) {
|
||||
var oldCommands = this.removeAfter(id);
|
||||
this.append(commands);
|
||||
return oldCommands;
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces all commands before the specified one.
|
||||
*
|
||||
* @param {function|string} id
|
||||
* Command function or name
|
||||
* @returns {object[]} Removed commands
|
||||
*/
|
||||
replaceBefore: function (id, commands) {
|
||||
var oldCommands = this.removeBefore(id);
|
||||
this.insertBefore(id, commands);
|
||||
return oldCommands;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all commands whose name match the specified regex.
|
||||
*
|
||||
* @param {regex} id_match
|
||||
* Regular expression to match command names.
|
||||
*/
|
||||
filterOut: function (id_match) {
|
||||
this.commands = this.commands.filter(c => !id_match.test(c.name));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function IsMacOSX10_6orOlder() {
|
||||
var is106orOlder = false;
|
||||
|
||||
@ -289,4 +541,3 @@ function IsMacOSX10_6orOlder() {
|
||||
}
|
||||
return is106orOlder;
|
||||
}
|
||||
|
||||
|
@ -4,127 +4,57 @@
|
||||
|
||||
function makeOffererNonTrickle(chain) {
|
||||
chain.replace('PC_LOCAL_SETUP_ICE_HANDLER', [
|
||||
['PC_LOCAL_SETUP_NOTRICKLE_ICE_HANDLER',
|
||||
function (test) {
|
||||
test.pcLocalWaitingForEndOfTrickleIce = false;
|
||||
// We need to install this callback before calling setLocalDescription
|
||||
// otherwise we might miss callbacks
|
||||
test.pcLocal.setupIceCandidateHandler(test, function () {
|
||||
// We ignore ICE candidates because we want the full offer
|
||||
} , function (label) {
|
||||
if (test.pcLocalWaitingForEndOfTrickleIce) {
|
||||
// This callback is needed for slow environments where ICE
|
||||
// trickling has not finished before the other side needs the
|
||||
// full SDP. In this case, this call to test.next() will complete
|
||||
// the PC_REMOTE_WAIT_FOR_OFFER step (see below).
|
||||
info("Looks like we were still waiting for Trickle to finish");
|
||||
// TODO replace this with a Promise
|
||||
test.next();
|
||||
}
|
||||
});
|
||||
// We can't wait for trickle to finish here as it will only start once
|
||||
// we have called setLocalDescription in the next step
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
function PC_LOCAL_SETUP_NOTRICKLE_ICE_HANDLER(test) {
|
||||
// We need to install this callback before calling setLocalDescription
|
||||
// otherwise we might miss callbacks
|
||||
test.pcLocal.setupIceCandidateHandler(test, () => {});
|
||||
// We ignore ICE candidates because we want the full offer
|
||||
}
|
||||
]);
|
||||
chain.replace('PC_REMOTE_GET_OFFER', [
|
||||
['PC_REMOTE_WAIT_FOR_OFFER',
|
||||
function (test) {
|
||||
if (test.pcLocal.endOfTrickleIce) {
|
||||
info("Trickle ICE finished already");
|
||||
test.next();
|
||||
} else {
|
||||
info("Waiting for trickle ICE to finish");
|
||||
test.pcLocalWaitingForEndOfTrickleIce = true;
|
||||
// In this case we rely on the callback from
|
||||
// PC_LOCAL_SETUP_NOTRICKLE_ICE_HANDLER above to proceed to the next
|
||||
// step once trickle is finished.
|
||||
}
|
||||
}
|
||||
],
|
||||
['PC_REMOTE_GET_FULL_OFFER',
|
||||
function (test) {
|
||||
function PC_REMOTE_GET_FULL_OFFER(test) {
|
||||
return test.pcLocal.endOfTrickleIce.then(() => {
|
||||
test._local_offer = test.pcLocal.localDescription;
|
||||
test._offer_constraints = test.pcLocal.constraints;
|
||||
test._offer_options = test.pcLocal.offerOptions;
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
]);
|
||||
chain.insertAfter('PC_REMOTE_SANE_REMOTE_SDP', [
|
||||
['PC_REMOTE_REQUIRE_REMOTE_SDP_CANDIDATES',
|
||||
function (test) {
|
||||
info("test.pcLocal.localDescription.sdp: " + JSON.stringify(test.pcLocal.localDescription.sdp));
|
||||
info("test._local_offer.sdp" + JSON.stringify(test._local_offer.sdp));
|
||||
ok(!test.localRequiresTrickleIce, "Local does NOT require trickle");
|
||||
ok(test._local_offer.sdp.contains("a=candidate"), "offer has ICE candidates")
|
||||
// TODO check for a=end-of-candidates once implemented
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
function PC_REMOTE_REQUIRE_REMOTE_SDP_CANDIDATES(test) {
|
||||
info("test.pcLocal.localDescription.sdp: " + JSON.stringify(test.pcLocal.localDescription.sdp));
|
||||
info("test._local_offer.sdp" + JSON.stringify(test._local_offer.sdp));
|
||||
ok(!test.localRequiresTrickleIce, "Local does NOT require trickle");
|
||||
ok(test._local_offer.sdp.contains("a=candidate"), "offer has ICE candidates")
|
||||
ok(test._local_offer.sdp.contains("a=end-of-candidates"), "offer has end-of-candidates");
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
function makeAnswererNonTrickle(chain) {
|
||||
chain.replace('PC_REMOTE_SETUP_ICE_HANDLER', [
|
||||
['PC_REMOTE_SETUP_NOTRICKLE_ICE_HANDLER',
|
||||
function (test) {
|
||||
test.pcRemoteWaitingForEndOfTrickleIce = false;
|
||||
// We need to install this callback before calling setLocalDescription
|
||||
// otherwise we might miss callbacks
|
||||
test.pcRemote.setupIceCandidateHandler(test, function () {
|
||||
// We ignore ICE candidates because we want the full answer
|
||||
}, function (label) {
|
||||
if (test.pcRemoteWaitingForEndOfTrickleIce) {
|
||||
// This callback is needed for slow environments where ICE
|
||||
// trickling has not finished before the other side needs the
|
||||
// full SDP. In this case this callback will call the step after
|
||||
// PC_LOCAL_WAIT_FOR_ANSWER
|
||||
info("Looks like we were still waiting for Trickle to finish");
|
||||
// TODO replace this with a Promise
|
||||
test.next();
|
||||
}
|
||||
});
|
||||
// We can't wait for trickle to finish here as it will only start once
|
||||
// we have called setLocalDescription in the next step
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
function PC_REMOTE_SETUP_NOTRICKLE_ICE_HANDLER(test) {
|
||||
// We need to install this callback before calling setLocalDescription
|
||||
// otherwise we might miss callbacks
|
||||
test.pcRemote.setupIceCandidateHandler(test, () => {});
|
||||
// We ignore ICE candidates because we want the full offer
|
||||
}
|
||||
]);
|
||||
chain.replace('PC_LOCAL_GET_ANSWER', [
|
||||
['PC_LOCAL_WAIT_FOR_ANSWER',
|
||||
function (test) {
|
||||
if (test.pcRemote.endOfTrickleIce) {
|
||||
info("Trickle ICE finished already");
|
||||
test.next();
|
||||
} else {
|
||||
info("Waiting for trickle ICE to finish");
|
||||
test.pcRemoteWaitingForEndOfTrickleIce = true;
|
||||
// In this case we rely on the callback from
|
||||
// PC_REMOTE_SETUP_NOTRICKLE_ICE_HANDLER above to proceed to the next
|
||||
// step once trickle is finished.
|
||||
}
|
||||
}
|
||||
],
|
||||
['PC_LOCAL_GET_FULL_ANSWER',
|
||||
function (test) {
|
||||
function PC_LOCAL_GET_FULL_ANSWER(test) {
|
||||
return test.pcRemote.endOfTrickleIce.then(() => {
|
||||
test._remote_answer = test.pcRemote.localDescription;
|
||||
test._answer_constraints = test.pcRemote.constraints;
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
]);
|
||||
chain.insertAfter('PC_LOCAL_SANE_REMOTE_SDP', [
|
||||
['PC_LOCAL_REQUIRE_REMOTE_SDP_CANDIDATES',
|
||||
function (test) {
|
||||
info("test.pcRemote.localDescription.sdp: " + JSON.stringify(test.pcRemote.localDescription.sdp));
|
||||
info("test._remote_answer.sdp" + JSON.stringify(test._remote_answer.sdp));
|
||||
ok(!test.remoteRequiresTrickleIce, "Remote does NOT require trickle");
|
||||
ok(test._remote_answer.sdp.contains("a=candidate"), "answer has ICE candidates")
|
||||
// TODO check for a=end-of-candidates once implemented
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
function PC_LOCAL_REQUIRE_REMOTE_SDP_CANDIDATES(test) {
|
||||
info("test.pcRemote.localDescription.sdp: " + JSON.stringify(test.pcRemote.localDescription.sdp));
|
||||
info("test._remote_answer.sdp" + JSON.stringify(test._remote_answer.sdp));
|
||||
ok(!test.remoteRequiresTrickleIce, "Remote does NOT require trickle");
|
||||
ok(test._remote_answer.sdp.contains("a=candidate"), "answer has ICE candidates")
|
||||
ok(test._remote_answer.sdp.contains("a=end-of-candidates"), "answer has end-of-candidates");
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user