зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1620073 - Tighten RTCPeerConnection-operations.https.html wpt tests. r=bwc
Differential Revision: https://phabricator.services.mozilla.com/D65631 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
eb96692383
Коммит
102768bbdd
|
@ -1,22 +1,4 @@
|
|||
[RTCPeerConnection-operations.https.html]
|
||||
[SLD(offer) must validate against LastCreatedOffer early (prerequisite)]
|
||||
[addIceCandidate must detect InvalidStateError synchronously when chain is empty]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1620448
|
||||
expected: FAIL
|
||||
|
||||
[setLocalDescription uses operations chain]
|
||||
expected: FAIL
|
||||
|
||||
[addIceCandidate uses operations chain]
|
||||
expected: [PASS, FAIL]
|
||||
|
||||
[setRemoteDescription uses operations chain]
|
||||
expected: FAIL
|
||||
|
||||
[SLD(offer) must validate input before signaling state (prerequisite)]
|
||||
expected: FAIL
|
||||
|
||||
[createAnswer uses operations chain]
|
||||
expected: FAIL
|
||||
|
||||
[createOffer uses operations chain]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -6,6 +6,187 @@
|
|||
<script>
|
||||
'use strict';
|
||||
|
||||
// Helpers to test APIs "return a promise rejected with a newly created" error.
|
||||
// Strictly speaking this means already-rejected upon return.
|
||||
function promiseState(p) {
|
||||
const t = {};
|
||||
return Promise.race([p, t])
|
||||
.then(v => (v === t)? "pending" : "fulfilled", () => "rejected");
|
||||
}
|
||||
|
||||
// However, to allow promises to be used in implementations, this helper adds
|
||||
// some slack: returning a pending promise will pass, provided it is rejected
|
||||
// before the end of the current run of the event loop (i.e. on microtask queue
|
||||
// before next task).
|
||||
async function promiseStateFinal(p) {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await promiseState(p);
|
||||
}
|
||||
return promiseState(p);
|
||||
}
|
||||
|
||||
[promiseState, promiseStateFinal].forEach(f => promise_test(async t => {
|
||||
assert_equals(await f(Promise.resolve()), "fulfilled");
|
||||
assert_equals(await f(Promise.reject()), "rejected");
|
||||
assert_equals(await f(new Promise(() => {})), "pending");
|
||||
}, `${f.name} helper works`));
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
await pc.setRemoteDescription(await pc.createOffer());
|
||||
const p = pc.createOffer();
|
||||
const haveState = promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidStateError");
|
||||
}
|
||||
assert_equals(await haveState, "rejected", "promise rejected on same task");
|
||||
}, "createOffer must detect InvalidStateError synchronously when chain is empty (prerequisite)");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const p = pc.createAnswer();
|
||||
const haveState = promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidStateError");
|
||||
}
|
||||
assert_equals(await haveState, "rejected", "promise rejected on same task");
|
||||
}, "createAnswer must detect InvalidStateError synchronously when chain is empty (prerequisite)");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const p = pc.setLocalDescription({type: "rollback"});
|
||||
const haveState = promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidStateError");
|
||||
}
|
||||
assert_equals(await haveState, "rejected", "promise rejected on same task");
|
||||
}, "SLD(rollback) must detect InvalidStateError synchronously when chain is empty");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const p = pc.addIceCandidate();
|
||||
const haveState = promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidStateError");
|
||||
}
|
||||
assert_equals(pc.remoteDescription, null, "no remote desciption");
|
||||
assert_equals(await haveState, "rejected", "promise rejected on same task");
|
||||
}, "addIceCandidate must detect InvalidStateError synchronously when chain is empty");
|
||||
|
||||
// Helper builds on above tests to check if operations queue is empty or not.
|
||||
async function isOperationsChainEmpty(pc) {
|
||||
let p, error;
|
||||
const signalingState = pc.signalingState;
|
||||
if (signalingState == "have-remote-offer") {
|
||||
p = pc.createOffer();
|
||||
} else {
|
||||
p = pc.createAnswer();
|
||||
}
|
||||
const state = await promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
// This helper tries to avoid side-effects by always failing,
|
||||
// but createAnswer above may succeed if chained after an SRD
|
||||
// that changes the signaling state on us. Ignore that success.
|
||||
if (signalingState == pc.signalingState) {
|
||||
assert_unreached("Control. Must not succeed");
|
||||
}
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidStateError",
|
||||
"isOperationsChainEmpty is working");
|
||||
}
|
||||
return state == "rejected";
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty to start");
|
||||
}, "isOperationsChainEmpty detects empty in stable");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
await pc.setLocalDescription(await pc.createOffer());
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty to start");
|
||||
}, "isOperationsChainEmpty detects empty in have-local-offer");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
await pc.setRemoteDescription(await pc.createOffer());
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty to start");
|
||||
}, "isOperationsChainEmpty detects empty in have-remote-offer");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const p = pc.createOffer();
|
||||
assert_false(await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "createOffer uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
await pc.setRemoteDescription(await pc.createOffer());
|
||||
const p = pc.createAnswer();
|
||||
assert_false(await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "createAnswer uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const offer = await pc.createOffer();
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty before");
|
||||
const p = pc.setLocalDescription(offer);
|
||||
assert_false(await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "setLocalDescription uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const offer = await pc.createOffer();
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty before");
|
||||
const p = pc.setRemoteDescription(offer);
|
||||
assert_false(await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "setRemoteDescription uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc1.close());
|
||||
const pc2 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc2.close());
|
||||
|
||||
pc1.addTransceiver("video");
|
||||
const offer = await pc1.createOffer();
|
||||
await pc1.setLocalDescription(offer);
|
||||
const {candidate} = await new Promise(r => pc1.onicecandidate = r);
|
||||
await pc2.setRemoteDescription(offer);
|
||||
const p = pc2.addIceCandidate(candidate);
|
||||
assert_false(await isOperationsChainEmpty(pc2), "Non-empty queue");
|
||||
await p;
|
||||
}, "addIceCandidate uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc1.close());
|
||||
|
@ -65,147 +246,4 @@ promise_test(async t => {
|
|||
await pc1.setRemoteDescription(pc2.localDescription);
|
||||
}, "Negotiate solely by operations queue and signaling state");
|
||||
|
||||
// Helpers to test APIs "return a promise rejected with a newly created" error.
|
||||
// Strictly speaking this means already-rejected upon return.
|
||||
function promiseState(p) {
|
||||
const t = {};
|
||||
return Promise.race([p, t])
|
||||
.then(v => (v === t)? "pending" : "fulfilled", () => "rejected");
|
||||
}
|
||||
|
||||
// However, to allow promises to be used in implementations, this helper adds
|
||||
// some slack: returning a pending promise will pass, provided it is rejected
|
||||
// before the end of the current run of the event loop (i.e. on microtask queue
|
||||
// before next task).
|
||||
async function promiseStateFinal(p) {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await promiseState(p);
|
||||
}
|
||||
return promiseState(p);
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
assert_equals(await promiseState(Promise.resolve()), "fulfilled");
|
||||
assert_equals(await promiseState(Promise.reject()), "rejected");
|
||||
assert_equals(await promiseState(new Promise(() => {})), "pending");
|
||||
}, "promiseState helper works");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
const p = pc.setLocalDescription({type: "offer", sdp: "blah"});
|
||||
const haveState = promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidModificationError");
|
||||
}
|
||||
assert_equals(await haveState, "rejected", "promise rejected on same task");
|
||||
}, "Operations chain must run first operation's sync part synchronously");
|
||||
|
||||
// Helper builds on above test to check if operations queue is empty or not.
|
||||
async function isOperationsChainEmpty(pc) {
|
||||
const type = pc.signalingState == "have-remote-offer" ? "answer" : "offer";
|
||||
const p = pc.setLocalDescription({type, sdp: "blah"});
|
||||
const state = await promiseStateFinal(p);
|
||||
try {
|
||||
await p;
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidModificationError", "isOperationsChainEmpty");
|
||||
}
|
||||
return state == "rejected";
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty to start");
|
||||
}, "isOperationsChainEmpty detects empty");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
pc.createDataChannel("dummy");
|
||||
const p = await pc.createOffer();
|
||||
try {
|
||||
await pc.setLocalDescription({type: "offer", sdp: "blah"});
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidModificationError");
|
||||
}
|
||||
}, "SLD(offer) must validate against LastCreatedOffer early (prerequisite)");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
pc.createDataChannel("dummy");
|
||||
await pc.setRemoteDescription(await pc.createOffer());
|
||||
try {
|
||||
await pc.setLocalDescription({type: "offer", sdp: "blah"});
|
||||
assert_unreached("Control. Must not succeed");
|
||||
} catch (e) {
|
||||
assert_equals(e.name, "InvalidModificationError");
|
||||
}
|
||||
}, "SLD(offer) must validate input before signaling state (prerequisite)");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
pc.createDataChannel("dummy");
|
||||
const p = pc.createOffer();
|
||||
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "createOffer uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
pc.createDataChannel("dummy");
|
||||
await pc.setRemoteDescription(await pc.createOffer());
|
||||
const p = pc.createAnswer();
|
||||
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "createAnswer uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
pc.createDataChannel("dummy");
|
||||
|
||||
const offer = await pc.createOffer();
|
||||
const p = pc.setLocalDescription(offer);
|
||||
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "setLocalDescription uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc.close());
|
||||
pc.createDataChannel("dummy");
|
||||
|
||||
const offer = await pc.createOffer();
|
||||
assert_true(await isOperationsChainEmpty(pc), "Empty after settled");
|
||||
const p = pc.setRemoteDescription(offer);
|
||||
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
|
||||
await p;
|
||||
}, "setRemoteDescription uses operations chain");
|
||||
|
||||
promise_test(async t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc1.close());
|
||||
const pc2 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc2.close());
|
||||
|
||||
pc1.addTransceiver("video");
|
||||
const offer = await pc1.createOffer();
|
||||
await pc1.setLocalDescription(offer);
|
||||
const {candidate} = await new Promise(r => pc1.onicecandidate = r);
|
||||
await pc2.setRemoteDescription(offer);
|
||||
const p = pc2.addIceCandidate(candidate);
|
||||
assert_true(!await isOperationsChainEmpty(pc2), "Non-empty queue");
|
||||
await p;
|
||||
}, "addIceCandidate uses operations chain");
|
||||
|
||||
</script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче