Bug 1119593 - Fixing test preconditions for steeplechase, r=drno

--HG--
extra : rebase_source : cc3b301447d312f7b7ab1a02340b8d4dbd750fb1
This commit is contained in:
Martin Thomson 2015-01-28 14:05:57 -08:00
Родитель c29940202d
Коммит 82713d6e5c
7 изменённых файлов: 166 добавлений и 258 удалений

Просмотреть файл

@ -11,12 +11,8 @@
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) {
resolve(event.target.result);
};
reader.onloadend = event => resolve(event.target.result);
reader.readAsText(blob);
});
}
@ -39,17 +35,11 @@ function addInitialDataChannel(chain) {
chain.insertBefore('PC_LOCAL_CHECK_MEDIA_TRACKS', [
function PC_LOCAL_VERIFY_DATA_CHANNEL_STATE(test) {
return test.pcLocal.dataChannels[0].opened
.then(() =>
ok(true, test.pcLocal + " dataChannels[0] switched to 'open'"));
return test.pcLocal.dataChannels[0].opened;
},
function PC_REMOTE_VERIFY_DATA_CHANNEL_STATE(test) {
return test.pcRemote.nextDataChannel
.then(channel => channel.opened)
.then(channel =>
is(channel.readyState, "open",
test.pcRemote + " dataChannels[0] switched to 'open'"));
return test.pcRemote.nextDataChannel.then(channel => channel.opened);
}
]);
chain.removeAfter('PC_REMOTE_CHECK_ICE_CONNECTIONS');
@ -169,7 +159,7 @@ function addInitialDataChannel(chain) {
function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL2(test) {
var channels = test.pcRemote.dataChannels;
var message = "I am the walrus; Goo goo g' joob";
var message = "I am the walrus; Goo goo g'joob";
return test.send(message).then(result => {
is(channels.indexOf(result.channel), channels.length - 1, "Last channel used");

Просмотреть файл

@ -114,21 +114,22 @@ function getUserMedia(constraints) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// These are the promises we use to track that the prerequisites for the test
// are in place before running it. Users of this file need to ensure that they
// also provide a promise called `scriptsReady` as well.
var setTestOptions;
var testConfigured = new Promise(r => setTestOptions = r);
/**
* Setup any Mochitest for WebRTC by enabling the preference for
* peer connections. As by bug 797979 it will also enable mozGetUserMedia()
* and disable the mozGetUserMedia() permission checking.
*
* @param {Function} aCallback
* Test method to execute after initialization
*/
function realRunTest(aCallback) {
if (window.SimpleTest) {
// Running as a Mochitest.
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
SpecialPowers.pushPrefEnv({'set': [
function setupEnvironment() {
if (!window.SimpleTest) {
return Promise.resolve();
}
// Running as a Mochitest.
SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
window.finish = () => SimpleTest.finish();
SpecialPowers.pushPrefEnv({
'set': [
['dom.messageChannel.enabled', true],
['media.peerconnection.enabled', true],
['media.peerconnection.identity.enabled', true],
@ -136,29 +137,39 @@ function realRunTest(aCallback) {
['media.peerconnection.default_iceservers', '[]'],
['media.navigator.permission.disabled', true],
['media.getusermedia.screensharing.enabled', true],
['media.getusermedia.screensharing.allowed_domains', "mochi.test"]]
}, function () {
try {
aCallback();
}
catch (err) {
generateErrorCallback()(err);
}
});
} else {
// Steeplechase, let it call the callback.
window.run_test = function(is_initiator) {
var options = {is_local: is_initiator,
is_remote: !is_initiator};
aCallback(options);
};
// Also load the steeplechase test code.
var s = document.createElement("script");
s.src = "/test.js";
document.head.appendChild(s);
}
['media.getusermedia.screensharing.allowed_domains', "mochi.test"]
]
}, setTestOptions);
}
// This is called by steeplechase; which provides the test configuration options
// directly to the test through this function. If we're not on steeplechase,
// the test is configured directly and immediately.
function run_test(is_initiator) {
var options = { is_local: is_initiator,
is_remote: !is_initiator };
// Also load the steeplechase test code.
var s = document.createElement("script");
s.src = "/test.js";
s.onload = () => setTestOptions(options);
document.head.appendChild(s);
}
function runTestWhenReady(testFunc) {
setupEnvironment();
return Promise.all([scriptsReady, testConfigured]).then(() => {
try {
return testConfigured.then(options => testFunc(options));
} catch (e) {
ok(false, 'Error executing test: ' + e +
((typeof e.stack === 'string') ?
(' ' + e.stack.split('\n').join(' ... ')) : ''));
}
});
}
/**
* Checks that the media stream tracks have the expected amount of tracks
* with the correct kind and id based on the type and constraints given.
@ -254,7 +265,7 @@ function generateErrorCallback(message) {
}
var unexpectedEventArrived;
var unexpectedEventArrivedPromise = new Promise((x, reject) => {
var rejectOnUnexpectedEvent = new Promise((x, reject) => {
unexpectedEventArrived = reject;
});
@ -279,11 +290,11 @@ function unexpectedEvent(message, eventName) {
}
/**
* Implements the event guard pattern used throughout. Each of the 'onxxx'
* Implements the one-shot event 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.
* That handler is used exactly once, after which the original, error-generating
* handler is re-installed. Thus, each event handler is used at most once.
*
* @param {object} wrapper
* The wrapper on which the psuedo-handler is installed
@ -291,20 +302,15 @@ function unexpectedEvent(message, eventName) {
* 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);
function createOneShotEventWrapper(wrapper, obj, event) {
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));
e.wrapper = wrapper;
wrapper[onx](e);
wrapper[onx] = unexpected;
};
}
@ -339,10 +345,7 @@ CommandChain.prototype = {
return prev.then(() => {
info('Run step ' + (i + 1) + ': ' + next.name);
return Promise.race([
next(this._framework),
unexpectedEventArrivedPromise
]);
return Promise.race([ next(this._framework), rejectOnUnexpectedEvent ]);
});
}, Promise.resolve())
.catch(e =>
@ -353,9 +356,6 @@ CommandChain.prototype = {
/**
* 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);
@ -363,45 +363,30 @@ CommandChain.prototype = {
/**
* 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);
indexOf: function(functionOrName) {
if (typeof functionOrName === 'string') {
return this.commands.findIndex(f => f.name === functionOrName);
}
return this.commands.indexOf(id);
return this.commands.indexOf(functionOrName);
},
/**
* 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);
insertAfter: function(functionOrName, commands) {
this._insertHelper(functionOrName, 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);
insertBefore: function(functionOrName, commands) {
this._insertHelper(functionOrName, commands, 0);
},
_insertHelper: function(id, commands, delta) {
delta = delta || 0;
var index = this.indexOf(id);
_insertHelper: function(functionOrName, commands, delta) {
var index = this.indexOf(functionOrName);
if (index >= 0) {
this.commands = [].concat(
@ -412,14 +397,10 @@ CommandChain.prototype = {
},
/**
* Removes the specified command
*
* @param {function|string} id
* Command function or name
* @returns {function[]} Removed commands
* Removes the specified command, returns what was removed.
*/
remove: function (id) {
var index = this.indexOf(id);
remove: function(functionOrName) {
var index = this.indexOf(functionOrName);
if (index >= 0) {
return this.commands.splice(index, 1);
}
@ -427,14 +408,10 @@ CommandChain.prototype = {
},
/**
* Removes all commands after the specified one.
*
* @param {function|string} id
* Command function or name
* @returns {function[]} Removed commands
* Removes all commands after the specified one, returns what was removed.
*/
removeAfter: function (id) {
var index = this.indexOf(id);
removeAfter: function(functionOrName) {
var index = this.indexOf(functionOrName);
if (index >= 0) {
return this.commands.splice(index + 1);
}
@ -442,14 +419,10 @@ CommandChain.prototype = {
},
/**
* Removes all commands before the specified one.
*
* @param {function|string} id
* Command function or name
* @returns {function[]} Removed commands
* Removes all commands before the specified one, returns what was removed.
*/
removeBefore: function (id) {
var index = this.indexOf(id);
removeBefore: function(functionOrName) {
var index = this.indexOf(functionOrName);
if (index >= 0) {
return this.commands.splice(0, index);
}
@ -457,50 +430,33 @@ CommandChain.prototype = {
},
/**
* Replaces a single command.
*
* @param {function|string} id
* Command function or name
* @param {function[]} commands
* List of commands
* @returns {function[]} Removed commands
* Replaces a single command, returns what was removed.
*/
replace: function (id, commands) {
this.insertBefore(id, commands);
return this.remove(id);
replace: function(functionOrName, commands) {
this.insertBefore(functionOrName, commands);
return this.remove(functionOrName);
},
/**
* Replaces all commands after the specified one.
*
* @param {function|string} id
* Command function or name
* @returns {object[]} Removed commands
* Replaces all commands after the specified one, returns what was removed.
*/
replaceAfter: function (id, commands) {
var oldCommands = this.removeAfter(id);
replaceAfter: function(functionOrName, commands) {
var oldCommands = this.removeAfter(functionOrName);
this.append(commands);
return oldCommands;
},
/**
* Replaces all commands before the specified one.
*
* @param {function|string} id
* Command function or name
* @returns {object[]} Removed commands
* Replaces all commands before the specified one, returns what was removed.
*/
replaceBefore: function (id, commands) {
var oldCommands = this.removeBefore(id);
this.insertBefore(id, commands);
replaceBefore: function(functionOrName, commands) {
var oldCommands = this.removeBefore(functionOrName);
this.insertBefore(functionOrName, 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));
@ -509,18 +465,17 @@ CommandChain.prototype = {
function IsMacOSX10_6orOlder() {
var is106orOlder = false;
if (navigator.platform.indexOf("Mac") !== 0) {
return false;
}
if (navigator.platform.indexOf("Mac") == 0) {
var version = Cc["@mozilla.org/system-info;1"]
.getService(Ci.nsIPropertyBag2)
.getProperty("version");
// the next line is correct: Mac OS 10.6 corresponds to Darwin version 10.x !
// Mac OS 10.7 is Darwin version 11.x. the |version| string we've got here
// is the Darwin version.
is106orOlder = (parseFloat(version) < 11.0);
}
return is106orOlder;
var version = Cc["@mozilla.org/system-info;1"]
.getService(Ci.nsIPropertyBag2)
.getProperty("version");
// the next line is correct: Mac OS 10.6 corresponds to Darwin version 10.x !
// Mac OS 10.7 is Darwin version 11.x. the |version| string we've got here
// is the Darwin version.
return (parseFloat(version) < 11.0);
}
(function(){

Просмотреть файл

@ -214,26 +214,23 @@ LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype
}
});
function runTest(f) {
// Use addEventListener to avoid SimpleTest hacking an .onload assignment
window.addEventListener('load', () => {
SimpleTest.waitForExplicitFinish();
realRunTest(f);
});
}
function createHTML(options) {
window.addEventListener('load', () => {
realCreateHTML(options);
});
}
[
var scriptsReady = Promise.all([
"/tests/SimpleTest/SimpleTest.js",
"head.js"
].forEach(script => {
console.log('msp');
].map(script => {
var el = document.createElement("script");
el.src = script;
document.head.appendChild(el);
});
return new Promise(r => el.onload = r);
}));
function createHTML(options) {
return scriptsReady.then(() => realCreateHTML(options));
}
function runTest(f) {
return scriptsReady.then(() => {
SimpleTest.waitForExplicitFinish();
return runTestWhenReady(f);
});
}

Просмотреть файл

@ -30,16 +30,16 @@ function isNetworkReady() {
var ip = ips.value[j];
// skip IPv6 address until bug 797262 is implemented
if (ip.indexOf(":") < 0) {
safeInfo("Network interface is ready with address: " + ip);
info("Network interface is ready with address: " + ip);
return true;
}
}
}
// ip address is not available
safeInfo("Network interface is not ready, required additional network setup");
info("Network interface is not ready, required additional network setup");
return false;
}
safeInfo("Network setup is not required");
info("Network setup is not required");
return true;
}
@ -117,5 +117,5 @@ function networkTestFinished() {
} else {
p = Promise.resolve();
}
return p.then(() => SimpleTest.finish());
return p.then(() => finish());
}

Просмотреть файл

@ -121,18 +121,6 @@ function removeVP8(sdp) {
return updated_sdp;
}
/**
* A wrapper around runTest() which handles B2G network setup and teardown
*/
function runNetworkTest(testFunction) {
// Use addEventListener to avoid SimpleTest hacking an .onload assignment
window.addEventListener('load', () => {
SimpleTest.waitForExplicitFinish();
startNetworkAndTest()
.then(() => realRunTest(testFunction));
});
}
/**
* This class handles tests for peer connections.
*
@ -242,7 +230,7 @@ PeerConnectionTest.prototype.closePC = function() {
* Close the open data channels, followed by the underlying peer connection
*/
PeerConnectionTest.prototype.close = function() {
var allChannels = (this.pcLocal ? this.pcLocal : this.pcRemote).dataChannels;
var allChannels = (this.pcLocal || this.pcRemote).dataChannels;
return timerGuard(
Promise.all(allChannels.map((channel, i) => this.closeDataChannels(i))),
60000, "failed to close data channels")
@ -317,8 +305,8 @@ PeerConnectionTest.prototype.send = function(data, options) {
return new Promise(resolve => {
// Register event handler for the target channel
target.onmessage = recv_data => {
resolve({ channel: target, data: recv_data });
target.onmessage = e => {
resolve({ channel: target, data: e.data });
};
source.send(data);
@ -621,13 +609,14 @@ function DataChannelWrapper(dataChannel, peerConnectionWrapper) {
/**
* Setup appropriate callbacks
*/
guardEvent(this, this._channel, 'close');
guardEvent(this, this._channel, 'error');
guardEvent(this, this._channel, 'message', e => e.data);
createOneShotEventWrapper(this, this._channel, 'close');
createOneShotEventWrapper(this, this._channel, 'error');
createOneShotEventWrapper(this, this._channel, 'message');
this.opened = timerGuard(new Promise(resolve => {
this._channel.onopen = () => {
this._channel.onopen = unexpectedEvent(this, 'onopen');
is(this.readyState, "open", "data channel is 'open' after 'onopen'");
resolve(this);
};
}), 60000, "channel didn't open in time");
@ -751,9 +740,7 @@ function PeerConnectionWrapper(label, configuration, h264) {
this.dataChannels = [ ];
this.onAddStreamAudioCounter = 0;
this.onAddStreamVideoCounter = 0;
this.addStreamCallbacks = {};
this.onAddStreamFired = false;
this._local_ice_candidates = [];
this._remote_ice_candidates = [];
@ -803,26 +790,15 @@ function PeerConnectionWrapper(label, configuration, h264) {
self.onAddStreamVideoCounter += event.stream.getVideoTracks().length;
}
this.attachMedia(event.stream, type, 'remote');
};
Object.keys(this.addStreamCallbacks).forEach(name => {
info(this + " calling addStreamCallback " + name);
this.addStreamCallbacks[name]();
});
};
guardEvent(this, this._pc, 'datachannel', e => {
createOneShotEventWrapper(this, this._pc, 'datachannel');
this._pc.addEventListener('datachannel', e => {
var wrapper = new DataChannelWrapper(e.channel, this);
this.dataChannels.push(wrapper);
return wrapper;
});
this.signalingStateCallbacks = {};
guardEvent(this, this._pc, 'signalingstatechange', e => {
Object.keys(this.signalingStateCallbacks).forEach(name => {
this.signalingStateCallbacks[name](e);
});
return e;
});
createOneShotEventWrapper(this, this._pc, 'signalingstatechange');
}
PeerConnectionWrapper.prototype = {
@ -961,9 +937,9 @@ PeerConnectionWrapper.prototype = {
*/
expectDataChannel: function(message) {
this.nextDataChannel = new Promise(resolve => {
this.ondatachannel = channel => {
ok(channel, message);
resolve(channel);
this.ondatachannel = e => {
ok(e.channel, message);
resolve(e.channel);
};
});
},
@ -1082,16 +1058,16 @@ PeerConnectionWrapper.prototype = {
*/
logSignalingState: function() {
this.signalingStateLog = [this._pc.signalingState];
this.signalingStateCallbacks.logSignalingStatus = e => {
this._pc.addEventListener('signalingstatechange', e => {
var newstate = this._pc.signalingState;
var oldstate = this.signalingStateLog[this.signalingStateLog.length - 1]
if (Object.keys(signalingStateTransitions).indexOf(oldstate) != -1) {
ok(signalingStateTransitions[oldstate].indexOf(newstate) != -1, this + ": legal signaling state transition from " + oldstate + " to " + newstate);
if (Object.keys(signalingStateTransitions).indexOf(oldstate) >= 0) {
ok(signalingStateTransitions[oldstate].indexOf(newstate) >= 0, this + ": legal signaling state transition from " + oldstate + " to " + newstate);
} else {
ok(false, this + ": old signaling state " + oldstate + " missing in signaling transition array");
}
this.signalingStateLog.push(newstate);
};
});
},
/**
@ -1376,7 +1352,7 @@ PeerConnectionWrapper.prototype = {
* The media constraints of the local and remote peer connection object
*/
checkMediaTracks : function(constraintsRemote) {
var _checkMediaTracks = constraintsRemote => {
var _checkMediaTracks = () => {
var localConstraintAudioTracks =
this.countAudioTracksInMediaConstraint(this.constraints);
var localStreams = this._pc.getLocalStreams();
@ -1412,27 +1388,14 @@ PeerConnectionWrapper.prototype = {
// TODO: remove this once Bugs 998552 and 998546 are closed
if (this.onAddStreamFired || (expectedRemoteTracks == 0)) {
_checkMediaTracks(constraintsRemote);
_checkMediaTracks();
return Promise.resolve();
}
info(this + " checkMediaTracks() got called before onAddStream fired");
// we rely on the outer mochitest timeout to catch the case where
// onaddstream never fires
var happy = new Promise(resolve => {
this.addStreamCallbacks.checkMediaTracks = resolve;
}).then(() => {
_checkMediaTracks(constraintsRemote);
});
var sad = wait(60000).then(() => {
if (!this.onAddStreamFired) {
// throw rather than call ok(false) because we only want this to be
// caught if the sad path fails the promise race with the happy path
throw new Error(this + " checkMediaTracks() timed out waiting" +
" for onaddstream event to fire");
}
});
return Promise.race([ happy, sad ]);
var checkPromise = new Promise(r => this._pc.addEventListener('addstream', r))
.then(_checkMediaTracks);
return timerGuard(checkPromise, 60000, "onaddstream never fired");
},
checkMsids: function() {
@ -1789,20 +1752,14 @@ PeerConnectionWrapper.prototype = {
}
};
function createHTML(options) {
window.addEventListener('load', () => {
realCreateHTML(options);
});
}
[
var scriptsReady = Promise.all([
"/tests/SimpleTest/SimpleTest.js",
"head.js",
"templates.js",
"turnConfig.js",
"dataChannel.js",
"network.js"
].forEach(script => {
].map(script => {
var el = document.createElement("script");
if (typeof scriptRelativePath === 'string' && script.charAt(0) !== "/") {
el.src = scriptRelativePath + script;
@ -1810,4 +1767,18 @@ function createHTML(options) {
el.src = script;
}
document.head.appendChild(el);
});
return new Promise(r => { el.onload = r; el.onerror = r; });
}));
function createHTML(options) {
return scriptsReady.then(() => realCreateHTML(options));
}
function runNetworkTest(testFunction) {
return scriptsReady.then(() => {
if (window.SimpleTest) {
SimpleTest.waitForExplicitFinish();
}
return startNetworkAndTest();
}).then(() => runTestWhenReady(testFunction));
}

Просмотреть файл

@ -2,6 +2,7 @@
support-files =
head.js
mediaStreamPlayback.js
network.js
pc.js
templates.js
turnConfig.js

Просмотреть файл

@ -78,7 +78,7 @@ function waitForIceConnected(test, pc) {
return pc.waitForIceConnected()
.then(() => {
info(pc + ": ICE connection state log: " + test.pcLocal.iceConnectionLog);
info(pc + ": ICE connection state log: " + pc.iceConnectionLog);
ok(pc.isIceConnected(), pc + ": ICE switched to 'connected' state");
});
}
@ -131,15 +131,9 @@ function checkTrackStats(pc, audio, outbound) {
});
}
function checkAllTrackStats(pc) {
var checks = [];
for (var i = 0; i < 4; ++i) {
// check all combinations
checks.push(checkTrackStats(pc, (i & 1) === 1, (i & 2) === 2));
}
return Promise.all(checks);
}
// checks all stats combinations inbound/outbound, audio/video
var checkAllTrackStats = pc =>
Promise.all([0, 1, 2, 3].map(i => checkTrackStats(pc, i & 1, i & 2)));
var commandsPeerConnection = [
function PC_SETUP_SIGNALING_CLIENT(test) {
@ -354,7 +348,7 @@ var commandsPeerConnection = [
return Promise.resolve();
}
test.getSignalingMessage("answer").then(message => {
return test.getSignalingMessage("answer").then(message => {
ok("answer" in message, "Got an answer message");
test._remote_answer = new mozRTCSessionDescription(message.answer);
test._answer_constraints = message.answer_constraints;
@ -440,7 +434,7 @@ var commandsPeerConnection = [
test.pcLocal.checkStatsIceConnections(stats,
test._offer_constraints,
test._offer_options,
test.originalAnswer);
test._remote_answer);
});
},
@ -464,6 +458,6 @@ var commandsPeerConnection = [
return checkAllTrackStats(test.pcLocal);
},
function PC_REMOTE_CHECK_STATS(test) {
return checkAllTrackStats(test.pcLocal);
return checkAllTrackStats(test.pcRemote);
}
];