Skip to content

Commit

Permalink
Add more detailed WPT test coverage for stopping behavior.
Browse files Browse the repository at this point in the history
The added tests aim to cover:
- When transceiver.recevier.track should or should not end.
- When transceiver is stopped locally versus remotely.
- When the transceiver is "stopping" or "stopped".
- When transceiver is stopped due to being removed in a rollback versus
  being rolled back without being removed.

The -expected.txt file reflects https://crbug.com/1315611 being fixed,
but the following issues have not been fixed yet which FAIL:
- https://crbug.com/1319913: direction is not updated to "stopped" if
  transceiver is stopped remotely.
- https://crbug.com/1319911: currentDirection is not updated to
  "stopped" when already "stopping" and answer removes the transceiver.

// Test coverage for the following issues:

Bug: 1315611, 1319902, 1319911, 1319913, 1319924
Change-Id: I8ad97221f5d2171384e8c7be32eaabd9811ba8f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3609997
Commit-Queue: Henrik Boström <hbos@chromium.org>
Reviewed-by: Harald Alvestrand <hta@chromium.org>
Cr-Commit-Position: refs/heads/main@{#997067}
  • Loading branch information
henbos authored and chromium-wpt-export-bot committed Apr 28, 2022
1 parent 850e501 commit 4c10acb
Showing 1 changed file with 217 additions and 0 deletions.
217 changes: 217 additions & 0 deletions webrtc/RTCRtpTransceiver-stopping.https.html
@@ -0,0 +1,217 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';

['audio', 'video'].forEach((kind) => {
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

const transceiver = pc1.addTransceiver(kind);

// Complete O/A exchange such that the transceiver gets associated.
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
assert_not_equals(transceiver.mid, null, 'mid before stop()');
assert_not_equals(transceiver.direction, 'stopped',
'direction before stop()');
assert_not_equals(transceiver.currentDirection, 'stopped',
'currentDirection before stop()');

// Stop makes it stopping, but not stopped.
transceiver.stop();
assert_not_equals(transceiver.mid, null, 'mid after stop()');
assert_equals(transceiver.direction, 'stopped', 'direction after stop()');
assert_not_equals(transceiver.currentDirection, 'stopped',
'currentDirection after stop()');

// Negotiating makes it stopped.
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
assert_equals(transceiver.mid, null, 'mid after negotiation');
assert_equals(transceiver.direction, 'stopped',
'direction after negotiation');
assert_equals(transceiver.currentDirection, 'stopped',
'currentDirection after negotiation');
}, `[${kind}] Locally stopped transceiver goes from stopping to stopped`);

promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());

const transceiver = pc.addTransceiver(kind);
const trackEnded = new Promise(
r => { transceiver.receiver.track.onended = () => { r(); } });
assert_equals(transceiver.receiver.track.readyState, 'live');
transceiver.stop();
// Stopping triggers ending the track, but this happens asynchronously.
assert_equals(transceiver.receiver.track.readyState, 'live');
await trackEnded;
assert_equals(transceiver.receiver.track.readyState, 'ended');
}, `[${kind}] Locally stopping a transceiver ends the track`);

promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

const pc1Transceiver = pc1.addTransceiver(kind);
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
const [pc2Transceiver] = pc2.getTransceivers();

pc1Transceiver.stop();

await pc1.setLocalDescription();
assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
// Applying the remote offer immediately ends the track, we don't need to
// create or apply an answer.
await pc2.setRemoteDescription(pc1.localDescription);
assert_equals(pc2Transceiver.receiver.track.readyState, 'ended');
}, `[${kind}] Remotely stopping a transceiver ends the track`);

promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

const pc1Transceiver = pc1.addTransceiver(kind);

// Complete O/A exchange such that the transceiver gets associated.
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
const [pc2Transceiver] = pc2.getTransceivers();
assert_not_equals(pc2Transceiver.mid, null, 'mid before stop()');
assert_not_equals(pc2Transceiver.direction, 'stopped',
'direction before stop()');
assert_not_equals(pc2Transceiver.currentDirection, 'stopped',
'currentDirection before stop()');

// Make the remote transceiver stopped.
pc1Transceiver.stop();

// Negotiating makes it stopped.
assert_equals(pc2.getTransceivers().length, 1);
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
// As soon as the remote offer is set, the transceiver is stopped but it is
// not disassociated or removed until setting the local answer.
assert_equals(pc2.getTransceivers().length, 1);
assert_not_equals(pc2Transceiver.mid, null, 'mid during negotiation');
assert_equals(pc2Transceiver.direction, 'stopped',
'direction during negotiation');
assert_equals(pc2Transceiver.currentDirection, 'stopped',
'currentDirection during negotiation');
await pc2.setLocalDescription();
assert_equals(pc2.getTransceivers().length, 0);
assert_equals(pc2Transceiver.mid, null, 'mid after negotiation');
assert_equals(pc2Transceiver.direction, 'stopped',
'direction after negotiation');
assert_equals(pc2Transceiver.currentDirection, 'stopped',
'currentDirection after negotiation');
}, `[${kind}] Remotely stopped transceiver goes directly to stopped`);

promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());

const transceiver = pc.addTransceiver(kind);

// Rollback does not end the track, because the transceiver is not removed.
await pc.setLocalDescription();
await pc.setLocalDescription({type:'rollback'});
assert_equals(transceiver.receiver.track.readyState, 'live');
}, `[${kind}] Rollback when transceiver is not removed does not end track`);

promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

const pc1Transceiver = pc1.addTransceiver(kind);

// Start negotiation, causing a transceiver to be created.
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
const [pc2Transceiver] = pc2.getTransceivers();

// Rollback such that the transceiver is removed.
await pc2.setLocalDescription({type:'rollback'});
assert_equals(pc2.getTransceivers().length, 0);
assert_equals(pc2Transceiver.receiver.track.readyState, 'ended');
}, `[${kind}] Rollback when removing transceiver does end the track`);

// Same test as above but looking at direction and currentDirection.
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

const pc1Transceiver = pc1.addTransceiver(kind);

// Start negotiation, causing a transceiver to be created.
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
const [pc2Transceiver] = pc2.getTransceivers();

// Rollback such that the transceiver is removed.
await pc2.setLocalDescription({type:'rollback'});
assert_equals(pc2.getTransceivers().length, 0);
// The removed transceiver is stopped.
assert_equals(pc2Transceiver.currentDirection, 'stopped',
'currentDirection indicate stopped');
// A stopped transceiver is necessarily also stopping.
assert_equals(pc2Transceiver.direction, 'stopped',
'direction indicate stopping');
// A stopped transceiver has no mid.
assert_equals(pc2Transceiver.mid, null, 'not associated');
}, `[${kind}] Rollback when removing transceiver makes it stopped`);

promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

const constraints = {};
constraints[kind] = true;
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const [track] = stream.getTracks();

pc1.addTrack(track);
pc2.addTrack(track);
const transceiver = pc2.getTransceivers()[0];

const ontrackEvent = new Promise(r => {
pc2.ontrack = e => r(e.track);
});

// Simulate glare: both peer connections set local offers.
await pc1.setLocalDescription();
await pc2.setLocalDescription();
// Set remote offer, which implicitly rolls back the local offer. Because
// `transceiver` is an addTrack-transceiver, it should get repurposed.
await pc2.setRemoteDescription(pc1.localDescription);
assert_equals(transceiver.receiver.track.readyState, 'live');
// Sanity check: the track should still be live when ontrack fires.
assert_equals((await ontrackEvent).readyState, 'live');
}, `[${kind}] Glare when transceiver is not removed does not end track`);
});
</script>

0 comments on commit 4c10acb

Please sign in to comment.